diff:
@@ -1922,7 +1922,7 @@ class AudioSignal(object):
self._effects_chain = []
return self
- def time_stretch(self, factor):
+ def time_stretch(self, factor, **kwargs):
"""
Adds a time stretch filter to the AudioSignal's effects chain.
A factor greater than one will shorten the signal, a factor less then one
@@ -1937,6 +1937,7 @@ class AudioSignal(object):
Args:
factor (float): Scaling factor for tempo change. Must be positive.
+ kwargs: Arugments passed to `sox.transform.tempo`
Returns:
self: Initial AudioSignal with updated effect chains
@@ -1945,10 +1946,10 @@ class AudioSignal(object):
* :func:`make_effect`: Syntactic sugar for adding an effect to the chain by name.
* :func:`reset_effects_chain`: Empties the effects chain without applying any effects.
"""
- self._effects_chain.append(effects.time_stretch(factor))
+ self._effects_chain.append(effects.time_stretch(factor, **kwargs))
return self
- def pitch_shift(self, n_semitones):
+ def pitch_shift(self, n_semitones, **kwargs):
"""
Add pitch shift effect to AudioSignal's effect chain.
A positive shift will change the pitch of the signal by `n_semitones`
@@ -1965,6 +1966,7 @@ class AudioSignal(object):
Args:
n_semitones (float): The number of semitones to shift the audio.
Positive values increases the frequency of the signal
+ kwargs: Arugments passed to `sox.transform.pitch`
Returns:
self: Initial AudioSignal with updated effect chains
@@ -1973,10 +1975,10 @@ class AudioSignal(object):
* :func:`make_effect`: Syntactic sugar for adding an effect to the chain by name.
* :func:`reset_effects_chain`: Empties the effects chain without applying any effects.
"""
- self._effects_chain.append(effects.pitch_shift(n_semitones))
+ self._effects_chain.append(effects.pitch_shift(n_semitones, **kwargs))
return self
- def low_pass(self, freq, poles=2, width_type="h", width=0.707):
+ def low_pass(self, freq, poles=2, width_type="h", width=0.707, **kwargs):
"""
Add low pass effect to AudioSignal's effect chain
@@ -1997,6 +1999,7 @@ class AudioSignal(object):
's': slope
'k': kHz
width (float): Band width in width_type units
+ kwargs: Arguments passed to `ffmpeg.filter`
Returns:
self: Initial AudioSignal with updated effect chains
@@ -2007,10 +2010,10 @@ class AudioSignal(object):
"""
self._effects_chain.append(effects.low_pass(freq, poles=poles,
width_type=width_type,
- width=width))
+ width=width, **kwargs))
return self
- def high_pass(self, freq, poles=2, width_type="h", width=0.707):
+ def high_pass(self, freq, poles=2, width_type="h", width=0.707, **kwargs):
"""
Add high pass effect to AudioSignal's effect chain
@@ -2031,6 +2034,7 @@ class AudioSignal(object):
's': slope
'k': kHz
width (float): Band width in width_type units
+ kwargs: Arguments passed to `ffmpeg.filter`
Returns:
self: Initial AudioSignal with updated effect chains
@@ -2041,10 +2045,10 @@ class AudioSignal(object):
"""
self._effects_chain.append(effects.high_pass(freq, poles=poles,
width_type=width_type,
- width=width))
+ width=width, **kwargs))
return self
- def tremolo(self, mod_freq, mod_depth):
+ def tremolo(self, mod_freq, mod_depth, **kwargs):
"""
Add tremolo effect to AudioSignal's effect chain
@@ -2058,6 +2062,7 @@ class AudioSignal(object):
Args:
mod_freq (float): Modulation frequency. Must be between .1 and 20000.
mod_depth (float): Modulation depth. Must be between 0 and 1.
+ kwargs: Arguments passed to `ffmpeg.filter`
Returns:
self: Initial AudioSignal with updated effect chains
@@ -2066,10 +2071,10 @@ class AudioSignal(object):
* :func:`make_effect`: Syntactic sugar for adding an effect to the chain by name.
* :func:`reset_effects_chain`: Empties the effects chain without applying any effects.
"""
- self._effects_chain.append(effects.tremolo(mod_freq, mod_depth))
+ self._effects_chain.append(effects.tremolo(mod_freq, mod_depth, **kwargs))
return self
- def vibrato(self, mod_freq, mod_depth):
+ def vibrato(self, mod_freq, mod_depth, **kwargs):
"""
Add vibrato effect to AudioSignal's effect chain.
@@ -2083,6 +2088,7 @@ class AudioSignal(object):
Args:
mod_freq (float): Modulation frequency. Must be between .1 and 20000.
mod_depth (float): Modulation depth. Must be between 0 and 1.
+ kwargs: Arguments passed to `ffmpeg.filter`
Returns:
self: Initial AudioSignal with updated effect chains
@@ -2091,10 +2097,11 @@ class AudioSignal(object):
* :func:`make_effect`: Syntactic sugar for adding an effect to the chain by name.
* :func:`reset_effects_chain`: Empties the effects chain without applying any effects.
"""
- self._effects_chain.append(effects.vibrato(mod_freq, mod_depth))
+ self._effects_chain.append(effects.vibrato(mod_freq, mod_depth, **kwargs))
return self
- def chorus(self, delays, decays, speeds, depths, in_gain=0.4, out_gain=0.4):
+ def chorus(self, delays, decays, speeds,
+ depths, in_gain=0.4, out_gain=0.4, **kwargs):
"""
Add chorus effect to AudioSignal's effect chain.
@@ -2112,6 +2119,7 @@ class AudioSignal(object):
depths (list of float): depths. Must be between 0 and 1
in_gain (float): Proportion of input gain. Must be between 0 and 1
out_gain (float): Proportion of output gain. Must be between 0 and 1
+ kwargs: Arguments passed to `ffmpeg.filter`
Returns:
self: Initial AudioSignal with updated effect chains
@@ -2123,11 +2131,12 @@ class AudioSignal(object):
self._effects_chain.append(effects.chorus(delays, decays,
speeds, depths,
in_gain=in_gain,
- out_gain=out_gain))
+ out_gain=out_gain,
+ **kwargs))
return self
def phaser(self, in_gain=0.4, out_gain=0.74, delay=3, decay=0.4,
- speed=0.5, type_="triangular"):
+ speed=0.5, type_="triangular", **kwargs):
"""
Add phaser effect to AudioSignal's effect chain
@@ -2147,6 +2156,7 @@ class AudioSignal(object):
type_ (str): modulation type. Either Triangular or Sinusoidal
"triangular" or "t" for Triangular
"sinusoidal" of "s" for sinusoidal
+ kwargs: Arguments passed to `ffmpeg.filter`
Returns:
self: Initial AudioSignal with updated effect chains
@@ -2156,12 +2166,12 @@ class AudioSignal(object):
* :func:`reset_effects_chain`: Empties the effects chain without applying any effects.
"""
fx = effects.phaser(in_gain=in_gain, out_gain=out_gain, delay=delay,
- decay=decay, speed=speed, type_=type_)
+ decay=decay, speed=speed, type_=type_, **kwargs)
self._effects_chain.append(fx)
return self
- def flanger(self, delay=0, depth=2, regen=0, width=71,
- speed=0.5, phase=25, shape="sinusoidal", interp="linear"):
+ def flanger(self, delay=0, depth=2, regen=0, width=71, speed=0.5,
+ phase=25, shape="sinusoidal", interp="linear", **kwargs):
"""
Add flanger effect to AudioSignal's effect chain
This is a FFmpeg effect. Please see
@@ -2182,6 +2192,7 @@ class AudioSignal(object):
shape (str): Swept wave shape, Must be "triangular" or "sinusoidal".
phase (float): swept wave percentage-shift for multi channel. Must be between 0 and 100.
interp (str): Delay Line interpolation. Must be "linear" or "quadratic".
+ kwargs: Arguments passed to `ffmpeg.filter`
Returns:
self: Initial AudioSignal with updated effect chains
@@ -2191,11 +2202,12 @@ class AudioSignal(object):
* :func:`reset_effects_chain`: Empties the effects chain without applying any effects.
"""
fx = effects.flanger(delay=delay, depth=depth, regen=regen, width=width,
- speed=speed, phase=phase, shape=shape, interp=interp)
+ speed=speed, phase=phase, shape=shape, interp=interp,
+ **kwargs)
self._effects_chain.append(fx)
return self
- def emphasis(self, level_in, level_out, type_="col", mode='production'):
+ def emphasis(self, level_in, level_out, type_="col", mode='production', **kwargs):
"""
Add emphasis effect to AudioSignal's effect chain. An emphasis filter boosts
frequency ranges the most susceptible to noise in a medium. When restoring
@@ -2226,6 +2238,7 @@ class AudioSignal(object):
mode (str): Filter mode. Must be one of the following:
- "reproduction": Apply de-emphasis filter
- "production": Apply emphasis filter
+ kwargs: Arguments passed to `ffmpeg.filter`
Returns:
self: Initial AudioSignal with updated effect chains
@@ -2235,12 +2248,13 @@ class AudioSignal(object):
* :func:`reset_effects_chain`: Empties the effects chain without applying any effects.
"""
self._effects_chain.append(effects.emphasis(level_in, level_out,
- type_=type_, mode=mode))
+ type_=type_, mode=mode,
+ **kwargs))
return self
def compressor(self, level_in, mode="downward", reduction_ratio=2,
attack=20, release=250, makeup=1, knee=2.8284, link="average",
- detection="rms", mix=1, threshold=0.125):
+ detection="rms", mix=1, threshold=0.125, **kwargs):
"""
Add compressor effect to AudioSignal's effect chain
@@ -2269,6 +2283,7 @@ class AudioSignal(object):
detection (str): Whether to process exact signal of the RMS of nearby signals.
Either "peak" for exact or "rms".
mix (float): Proportion of compressed signal in output.
+ kwargs: Arguments passed to `ffmpeg.filter`
Returns:
self: Initial AudioSignal with updated effect chains
@@ -2279,11 +2294,11 @@ class AudioSignal(object):
"""
fx = effects.compressor(level_in, mode=mode, reduction_ratio=reduction_ratio,
attack=attack, release=release, makeup=makeup, knee=knee, link=link,
- detection=detection, mix=mix, threshold=threshold)
+ detection=detection, mix=mix, threshold=threshold, **kwargs)
self._effects_chain.append(fx)
return self
- def equalizer(self, bands):
+ def equalizer(self, bands, **kwargs):
"""
Add eqaulizer effect to AudioSignal's effect chain
@@ -2312,7 +2327,7 @@ class AudioSignal(object):
* :func:`make_effect`: Syntactic sugar for adding an effect to the chain by name.
* :func:`reset_effects_chain`: Empties the effects chain without applying any effects.
"""
- self._effects_chain.append(effects.equalizer(bands))
+ self._effects_chain.append(effects.equalizer(bands, **kwargs))
return self
##################################################

diff:
@@ -182,6 +182,7 @@ def time_stretch(factor, **kwargs):
for details.
Args:
factor (float): Scaling factor for tempo change. Must be positive.
+ kwargs: Arugments passed to `sox.transform.tempo`
Returns:
filter (SoXFilter): A SoXFilter object, to be called on an pysndfx stream
"""
@@ -206,6 +207,7 @@ def pitch_shift(n_semitones, **kwargs):
Args:
n_semitones (float): The number of semitones to shift the audio.
Positive values increases the frequency of the signal
+ kwargs: Arugments passed to `sox.transform.pitch`
Returns:
filter (SoxFilter): A SoXFilter object, to be called on an pysndfx stream
"""
@@ -223,7 +225,7 @@ def _pass_arg_check(freq, poles, width_type, width):
raise ValueError("width should be positive scalar")
-def low_pass(freq, poles=2, width_type="h", width=.707):
+def low_pass(freq, poles=2, width_type="h", width=.707, **kwargs):
"""
Creates an FFmpegFilter object, which when called on an ffmpeg stream,
applies a low pass filter to the audio signal.
@@ -243,19 +245,21 @@ def low_pass(freq, poles=2, width_type="h", width=.707):
's': slope
'k': kHz
width (float): Band width in width_type units
+ kwargs: Arguments passed to `ffmpeg.filter`
Returns:
filter (FFmpegFilter): Resulting filter, to be called on a ffmpeg stream
"""
_pass_arg_check(freq, poles, width_type, width)
filter_ = FFmpegFilter("low_pass", ffmpeg_name="lowpass", f=freq, p=poles,
- t=width_type, w=width)
+ t=width_type, w=width, **kwargs)
filter_.params = {"freq": freq, "poles": poles,
- "width_type": width_type, "width": width}
+ "width_type": width_type, "width": width, **kwargs}
+
return filter_
-def high_pass(freq, poles=2, width_type="h", width=.707):
+def high_pass(freq, poles=2, width_type="h", width=.707, **kwargs):
"""
Creates a FFmpegFilter object, when called on an ffmpeg stream,
applies a high pass filter to the audio signal.
@@ -275,19 +279,20 @@ def high_pass(freq, poles=2, width_type="h", width=.707):
's': slope
'k': kHz
width (float): Band width in width_type units
+ kwargs: Arguments passed to `ffmpeg.filter`
Returns:
filter (FFmpegFilter): Resulting filter, to be called on a ffmpeg stream
"""
_pass_arg_check(freq, poles, width_type, width)
filter_ = FFmpegFilter("high_pass", ffmpeg_name="highpass", f=freq, p=poles,
- t=width_type, w=width)
+ t=width_type, w=width, **kwargs)
filter_.params = {"freq": freq, "poles": poles,
- "width_type": width_type, "width": width}
+ "width_type": width_type, "width": width, **kwargs}
return filter_
-def tremolo(mod_freq, mod_depth):
+def tremolo(mod_freq, mod_depth, **kwargs):
"""
Creates a FFmpegFilter object, when called on an ffmpeg stream,
applies a tremolo filter to the audio signal
@@ -300,6 +305,7 @@ def tremolo(mod_freq, mod_depth):
Args:
mod_freq (float): Modulation frequency. Must be between .1 and 20000.
mod_depth (float): Modulation depth. Must be between 0 and 1.
+ kwargs: Arguments passed to `ffmpeg.filter`
Returns:
filter (FFmpegFilter): Resulting filter, to be called on a ffmpeg stream
"""
@@ -309,12 +315,12 @@ def tremolo(mod_freq, mod_depth):
if not np.issubdtype(type(mod_depth), np.number) or mod_depth < 0 or mod_depth > 1:
raise ValueError("mod_depth should be positve scalar between 0 and 1.")
- filter_ = FFmpegFilter("tremolo", f=mod_freq, d=mod_depth)
- filter_.params = {"mod_freq": mod_freq, "mod_depth": mod_depth}
+ filter_ = FFmpegFilter("tremolo", f=mod_freq, d=mod_depth, **kwargs)
+ filter_.params = {"mod_freq": mod_freq, "mod_depth": mod_depth, **kwargs}
return filter_
-def vibrato(mod_freq, mod_depth):
+def vibrato(mod_freq, mod_depth, **kwargs):
"""
Creates a FFmpegFilter object, when called on an ffmpeg stream,
applies a vibrato filter to the audio signal.
@@ -327,6 +333,7 @@ def vibrato(mod_freq, mod_depth):
Args:
mod_freq (float): Modulation frequency. Must be between .1 and 20000.
mod_depth (float): Modulation depth. Must be between 0 and 1.
+ kwargs: Arguments passed to `ffmpeg.filter`
Returns:
filter (FFmpegFilter): Resulting filter, to be called on a ffmpeg stream
"""
@@ -336,13 +343,13 @@ def vibrato(mod_freq, mod_depth):
if not np.issubdtype(type(mod_depth), np.number) or mod_depth < 0 or mod_depth > 1:
raise ValueError("mod_depth should be positve scalar between 0 and 1.")
- filter_ = FFmpegFilter("vibrato", f=mod_freq, d=mod_depth)
- filter_.params = {"mod_freq": mod_freq, "mod_depth": mod_depth}
+ filter_ = FFmpegFilter("vibrato", f=mod_freq, d=mod_depth, **kwargs)
+ filter_.params = {"mod_freq": mod_freq, "mod_depth": mod_depth, **kwargs}
return filter_
def chorus(delays, decays, speeds, depths,
- in_gain=.4, out_gain=.4):
+ in_gain=.4, out_gain=.4, **kwargs):
"""
Creates a FFmpegFilter object, when called on an ffmpeg stream,
applies a vibrato filter to the audio signal.
@@ -360,6 +367,7 @@ def chorus(delays, decays, speeds, depths,
depths (list of float): depths. Must be between 0 and 1
in_gain (float): Proportion of input gain. Must be between 0 and 1
out_gain (float): Proportion of output gain. Must be between 0 and 1
+ kwargs: Arguments passed to `ffmpeg.filter`
Returns:
filter: Resulting filter, to be called on a ffmpeg stream
"""
@@ -383,17 +391,18 @@ def chorus(delays, decays, speeds, depths,
filter_ = FFmpegFilter("chorus", in_gain=in_gain,
out_gain=out_gain, delays=ffmpeg_delays,
speeds=ffmpeg_speeds, decays=ffmpeg_decays,
- depths=ffmpeg_depths)
+ depths=ffmpeg_depths, **kwargs)
filter_.params["delays"] = delays
filter_.params["speeds"] = speeds
filter_.params["decays"] = decays
filter_.params["depths"] = depths
+ filter_.params.update(kwargs)
return filter_
def phaser(in_gain=.4, out_gain=.74, delay=3,
- decay=.4, speed=.5, type_="triangular"):
+ decay=.4, speed=.5, type_="triangular", **kwargs):
"""
Creates a FFmpegFilter object, when called on an ffmpeg stream,
applies a phaser filter to the audio signal
@@ -412,6 +421,7 @@ def phaser(in_gain=.4, out_gain=.74, delay=3,
type_ (str): modulation type. Either Triangular or Sinusoidal
"triangular" or "t" for Triangular
"sinusoidal" of "s" for sinusoidal
+ kwargs: Arguments passed to `ffmpeg.filter`
Returns:
filter (FFmpegFilter): Resulting filter, to be called on a ffmpeg stream
"""
@@ -427,16 +437,15 @@ def phaser(in_gain=.4, out_gain=.74, delay=3,
raise ValueError(f"_type must be one of the following:\n{allowed_mod_types}")
# type is reserved word in python, kwarg dict is necessary
- type_kwarg = {
- "type": type_
- }
+ type_kwarg = {"type": type_}
filter_ = FFmpegFilter("phaser", ffmpeg_name="aphaser", in_gain=in_gain, out_gain=out_gain,
- delay=delay, speed=speed, decay=decay, **type_kwarg)
+ delay=delay, speed=speed, decay=decay, **{**type_kwarg, **kwargs})
filter_.params = copy.deepcopy(filter_.params)
del filter_.params["type"]
filter_.params["type_"] = type_
+ filter_.params.update(kwargs)
return filter_
@@ -473,8 +482,8 @@ def _flanger_argcheck(delay, depth, regen, width,
raise ValueError(error_text)
-def flanger(delay=0, depth=2, regen=0, width=71,
- speed=.5, phase=25, shape="sinusoidal", interp="linear"):
+def flanger(delay=0, depth=2, regen=0, width=71, speed=.5,
+ phase=25, shape="sinusoidal", interp="linear", **kwargs):
"""
Creates a FFmpegFilter object, when called on an ffmpeg stream,
applies a flanger filter to the audio signal.
@@ -495,6 +504,7 @@ def flanger(delay=0, depth=2, regen=0, width=71,
shape (str): Swept wave shape, Must be "triangular" or "sinusoidal".
phase (float): swept wave percentage-shift for multi channel. Must be between 0 and 100.
interp (str): Delay Line interpolation. Must be "linear" or "quadratic".
+ kwargs: Arguments passed to `ffmpeg.filter`
Returns:
filter (FFmpegFilter): Resulting filter, to be called on a ffmpeg stream
"""
@@ -502,10 +512,10 @@ def flanger(delay=0, depth=2, regen=0, width=71,
_flanger_argcheck(delay, depth, regen, width, speed, phase, shape, interp)
return FFmpegFilter("flanger", delay=delay, depth=depth, regen=regen, width=width,
- speed=speed, phase=phase, shape=shape, interp=interp)
+ speed=speed, phase=phase, shape=shape, interp=interp, **kwargs)
-def emphasis(level_in, level_out, type_="col", mode='production'):
+def emphasis(level_in, level_out, type_="col", mode='production', **kwargs):
"""
Creates a FFmpegFilter object, when called on an ffmpeg stream,
applies a emphasis filter to the audio signal. An emphasis filter boosts frequency ranges
@@ -533,6 +543,7 @@ def emphasis(level_in, level_out, type_="col", mode='production'):
mode (str): Filter mode. Must be one of the following:
- "reproduction": Apply de-emphasis filter
- "production": Apply emphasis filter
+ kwargs: Arguments passed to `ffmpeg.filter`
Returns:
filter (FFmpeg Filter): Resulting filter, to be called on a ffmpeg stream
"""
@@ -548,16 +559,15 @@ def emphasis(level_in, level_out, type_="col", mode='production'):
raise ValueError(f"mode must be production or reproduction")
# type is a reserved word in python, so kwarg dict is necessary
- type_kwarg = {
- 'type': type_
- }
+ type_kwarg = {'type': type_}
filter_ = FFmpegFilter("emphasis", ffmpeg_name="aemphasis", level_in=level_in,
- level_out=level_out, mode=mode, **type_kwarg)
+ level_out=level_out, mode=mode, **{**type_kwarg, **kwargs})
filter_.params = copy.deepcopy(filter_.params)
del filter_.params["type"]
filter_.params["type_"] = type_
+ filter_.params.update(kwargs)
return filter_
@@ -607,7 +617,7 @@ def _compressor_argcheck(level_in, mode, reduction_ratio,
def compressor(level_in, mode="downward", reduction_ratio=2,
attack=20, release=250, makeup=1, knee=2.8284, link="average",
- detection="rms", mix=1, threshold=.125):
+ detection="rms", mix=1, threshold=.125, **kwargs):
"""
Creates a FFmpegFilter object, when called on an ffmpeg stream,
applies a compressor filter to the audio signal.
@@ -634,6 +644,7 @@ def compressor(level_in, mode="downward", reduction_ratio=2,
detection (str): Whether to process exact signal of the RMS of nearby signals.
Either "peak" for exact or "rms".
mix (float): Proportion of compressed signal in output.
+ kwargs: Arguments passed to `ffmpeg.filter`
Returns:
filter (FFmpegFilter): Resulting filter, to be called on a ffmpeg stream
"""
@@ -642,14 +653,16 @@ def compressor(level_in, mode="downward", reduction_ratio=2,
filter_ = FFmpegFilter("compressor", ffmpeg_name="acompressor", level_in=level_in,
ratio=reduction_ratio, attack=attack, release=release, makeup=makeup,
- knee=knee, link=link, detection=detection, mix=mix, threshold=threshold)
+ knee=knee, link=link, detection=detection, mix=mix, threshold=threshold,
+ **kwargs)
filter_.params = copy.deepcopy(filter_.params)
del filter_.params["ratio"]
filter_.params["reduction_ratio"] = reduction_ratio
+ filter_.params.update(**kwargs)
return filter_
-def equalizer(bands):
+def equalizer(bands, **kwargs):
"""
Creates a FFmpegFilter object, when called on an ffmpeg stream,
applies a equalizer filter to the audio signal.
@@ -670,6 +683,7 @@ def equalizer(bands):
0, for Butterworth
1, for Chebyshev type 1
2, for Chebyshev type 2
+ kwargs: Arguments passed to `ffmpeg.filter`
Returns:
filter: Resulting filter, to be called on a ffmpeg stream
"""
@@ -694,6 +708,6 @@ def equalizer(bands):
for band in bands
])
- filter_ = FFmpegFilter("equalizer", ffmpeg_name="anequalizer", params=params)
- filter_.params = {"bands": bands}
+ filter_ = FFmpegFilter("equalizer", ffmpeg_name="anequalizer", params=params, **kwargs)
+ filter_.params = {"bands": bands, **kwargs}
return filter_

diff:
@@ -514,3 +514,10 @@ def test_order(mix_and_sources):
sox_ffmpeg_order = ["time_stretch", "pitch_shift", "tremolo", "vibrato"]
for effect, _filter in zip(signal_sf.effects_applied, sox_ffmpeg_order):
assert effect.filter == _filter
+
+def test_kwargs():
+ kwargs = {"a":1, "b":2, "c":3}
+ filter_func = effects.time_stretch(1.3, **kwargs)
+ for key, value in kwargs.items():
+ assert filter_func.params[key] == value
+ assert filter_func.params["factor"] == 1.3
\ No newline at end of file

original_message:
Added kwargs to effects and effects hooks (#186)

Co-authored-by: Andreas Bugler


message: Add the ability to force the parsing of previousily generated files

change_type: MODIFY

diff:
@@ -151,6 +151,7 @@ struct Configuration {
let source: Source
let templates: Paths
let output: Path
+ let parsableExtensions: [String]
let args: [String: NSObject]
init(path: Path, relativePath: Path) throws {
@@ -182,6 +183,8 @@ struct Configuration {
}
self.templates = templates
+ self.parsableExtensions = dict["parsableExtensions"] as? [String] ?? []
+
guard let output = dict["output"] as? String else {
throw Configuration.Error.invalidOutput(message: "'output' key is missing or is not a string.")
}
@@ -190,10 +193,11 @@ struct Configuration {
self.args = dict["args"] as? [String: NSObject] ?? [:]
}
- init(sources: [Path], templates: [Path], output: Path, args: [String: NSObject]) {
+ init(sources: [Path], templates: [Path], output: Path, parsableExtensions: [String], args: [String: NSObject]) {
self.source = .sources(Paths(include: sources))
self.templates = Paths(include: templates)
self.output = output
+ self.parsableExtensions = parsableExtensions
self.args = args
}

diff:
@@ -16,10 +16,16 @@ enum Verifier {
case approved
}
- public static func canParse(content: String) -> Result {
+ public static func canParse(content: String,
+ path: Path,
+ parsableExtensions: [String]) -> Result {
guard !content.isEmpty else { return .approved }
- if content.hasPrefix(Sourcery.generationMarker) {
+ let hasParsableExtension = (parsableExtensions.filter({ (ext) -> Bool in
+ return path.hasExtension(as: ext)
+ }).isEmpty == false)
+
+ if content.hasPrefix(Sourcery.generationMarker) && hasParsableExtension == false {
return .isCodeGenerated
}

diff:
@@ -47,9 +47,10 @@ class Sourcery {
/// - files: Path of files to process, can be directory or specific file.
/// - templatePath: Specific Template to use for code generation.
/// - output: Path to output source code to.
+ /// - parsableExtensions: extensions of generated sourcery file that can be parsed
/// - watcherEnabled: Whether daemon watcher should be enabled.
/// - Throws: Potential errors.
- func processFiles(_ source: Source, usingTemplates templatesPaths: Paths, output: Path) throws -> [FolderWatcher.Local]? {
+ func processFiles(_ source: Source, usingTemplates templatesPaths: Paths, output: Path, parsableExtensions: [String]) throws -> [FolderWatcher.Local]? {
self.templatesPaths = templatesPaths
self.outputPath = output
@@ -66,7 +67,7 @@ class Sourcery {
var result: ParsingResult
switch source {
case let .sources(paths):
- result = try self.parse(from: paths.include, exclude: paths.exclude, modules: nil)
+ result = try self.parse(from: paths.include, exclude: paths.exclude, parsableExtensions: parsableExtensions, modules: nil)
case let .projects(projects):
var paths = [Path]()
var modules = [String]()
@@ -195,12 +196,16 @@ class Sourcery {
extension Sourcery {
typealias ParsingResult = (types: Types, inlineRanges: [(file: String, ranges: [String: NSRange])])
- fileprivate func parse(from: [Path], exclude: [Path] = [], modules: [String]?) throws -> ParsingResult {
+ fileprivate func parse(from: [Path], exclude: [Path] = [], parsableExtensions: [String] = [], modules: [String]?) throws -> ParsingResult {
if let modules = modules {
precondition(from.count == modules.count, "There should be module for each file to parse")
}
self.track("Scanning sources...", terminator: "")
+ self.track("Parsable Extensions:", terminator: "")
+ for ext in parsableExtensions {
+ self.track("ext: \(ext)", terminator: "")
+ }
var inlineRanges = [(file: String, ranges: [String: NSRange])]()
var allResults = [FileParserResult]()
@@ -216,7 +221,9 @@ extension Sourcery {
}
.map { (path: $0, contents: try $0.read(.utf8)) }
.filter {
- let result = Verifier.canParse(content: $0.contents)
+ let result = Verifier.canParse(content: $0.contents,
+ path: $0.path,
+ parsableExtensions: parsableExtensions)
if result == .containsConflictMarkers {
throw Error.containsMergeConflictMarkers
}

diff:
@@ -40,6 +40,11 @@ extension Path {
return !self.isDirectory && self.extension == "swift"
}
+ func hasExtension(as string: String) -> Bool {
+ let extensionString = ".\(string)."
+ return self.string.contains(extensionString)
+ }
+
init(_ string: String, relativeTo relativePath: Path) {
var path = Path(string)
if !path.isAbsolute {

diff:
@@ -125,8 +125,9 @@ func runCLI() {
VariadicOption<Path>("templates", description: "Path to templates. File or Directory."),
Option<Path>("output", ".", description: "Path to output. File or Directory. Default is current path."),
Option<Path>("config", ".", description: "Path to config file. File or Directory. Default is current path."),
+ VariadicOption<String>("parsableExtensions", description: "extensions that this run of Sorcery should parse"),
Argument<CustomArguments>("args", description: "Custom values to pass to templates.")
- ) { watcherEnabled, disableCache, verboseLogging, quiet, prune, sources, templates, output, configPath, args in
+ ) { watcherEnabled, disableCache, verboseLogging, quiet, prune, sources, templates, output, configPath, parsableExtensions, args in
do {
Log.level = verboseLogging ? .verbose : quiet ? .errors : .info
@@ -138,6 +139,7 @@ func runCLI() {
configuration = Configuration(sources: sources,
templates: templates,
output: output,
+ parsableExtensions: parsableExtensions,
args: args.arguments)
} else {
_ = Validators.isFileOrDirectory(path: configPath)
@@ -164,7 +166,8 @@ func runCLI() {
if let keepAlive = try sourcery.processFiles(
configuration.source,
usingTemplates: configuration.templates,
- output: configuration.output) {
+ output: configuration.output,
+ parsableExtensions: configuration.parsableExtensions) {
RunLoop.current.run()
_ = keepAlive
} else {

license: MIT License

repo: krzysztofzablocki/sourcery


message: Rename `parsableExtensions` to `forceParse`

diff:
@@ -151,7 +151,7 @@ struct Configuration {
let source: Source
let templates: Paths
let output: Path
- let parsableExtensions: [String]
+ let forceParse: [String]
let args: [String: NSObject]
init(path: Path, relativePath: Path) throws {
@@ -183,7 +183,7 @@ struct Configuration {
}
self.templates = templates
- self.parsableExtensions = dict["parsableExtensions"] as? [String] ?? []
+ self.forceParse = dict["force-parse"] as? [String] ?? []
guard let output = dict["output"] as? String else {
throw Configuration.Error.invalidOutput(message: "'output' key is missing or is not a string.")
@@ -193,11 +193,11 @@ struct Configuration {
self.args = dict["args"] as? [String: NSObject] ?? [:]
}
- init(sources: [Path], templates: [Path], output: Path, parsableExtensions: [String], args: [String: NSObject]) {
+ init(sources: [Path], templates: [Path], output: Path, forceParse: [String], args: [String: NSObject]) {
self.source = .sources(Paths(include: sources))
self.templates = Paths(include: templates)
self.output = output
- self.parsableExtensions = parsableExtensions
+ self.forceParse = forceParse
self.args = args
}

diff:
@@ -18,10 +18,10 @@ enum Verifier {
public static func canParse(content: String,
path: Path,
- parsableExtensions: [String]) -> Result {
+ forceParse: [String] = []) -> Result {
guard !content.isEmpty else { return .approved }
- let hasParsableExtension = (parsableExtensions.filter({ (ext) -> Bool in
+ let hasParsableExtension = (forceParse.filter({ (ext) -> Bool in
return path.hasExtension(as: ext)
}).isEmpty == false)

diff:
@@ -47,10 +47,10 @@ class Sourcery {
/// - files: Path of files to process, can be directory or specific file.
/// - templatePath: Specific Template to use for code generation.
/// - output: Path to output source code to.
- /// - parsableExtensions: extensions of generated sourcery file that can be parsed
+ /// - forceParse: extensions of generated sourcery file that can be parsed
/// - watcherEnabled: Whether daemon watcher should be enabled.
/// - Throws: Potential errors.
- func processFiles(_ source: Source, usingTemplates templatesPaths: Paths, output: Path, parsableExtensions: [String]) throws -> [FolderWatcher.Local]? {
+ func processFiles(_ source: Source, usingTemplates templatesPaths: Paths, output: Path, forceParse: [String] = []) throws -> [FolderWatcher.Local]? {
self.templatesPaths = templatesPaths
self.outputPath = output
@@ -67,7 +67,7 @@ class Sourcery {
var result: ParsingResult
switch source {
case let .sources(paths):
- result = try self.parse(from: paths.include, exclude: paths.exclude, parsableExtensions: parsableExtensions, modules: nil)
+ result = try self.parse(from: paths.include, exclude: paths.exclude, forceParse: forceParse, modules: nil)
case let .projects(projects):
var paths = [Path]()
var modules = [String]()
@@ -196,16 +196,13 @@ class Sourcery {
extension Sourcery {
typealias ParsingResult = (types: Types, inlineRanges: [(file: String, ranges: [String: NSRange])])
- fileprivate func parse(from: [Path], exclude: [Path] = [], parsableExtensions: [String] = [], modules: [String]?) throws -> ParsingResult {
+ fileprivate func parse(from: [Path], exclude: [Path] = [], forceParse: [String] = [], modules: [String]?) throws -> ParsingResult {
if let modules = modules {
precondition(from.count == modules.count, "There should be module for each file to parse")
}
self.track("Scanning sources...", terminator: "")
- self.track("Parsable Extensions:", terminator: "")
- for ext in parsableExtensions {
- self.track("ext: \(ext)", terminator: "")
- }
+
var inlineRanges = [(file: String, ranges: [String: NSRange])]()
var allResults = [FileParserResult]()
@@ -223,7 +220,7 @@ extension Sourcery {
.filter {
let result = Verifier.canParse(content: $0.contents,
path: $0.path,
- parsableExtensions: parsableExtensions)
+ forceParse: forceParse)
if result == .containsConflictMarkers {
throw Error.containsMergeConflictMarkers
}

diff:
@@ -125,9 +125,9 @@ func runCLI() {
VariadicOption<Path>("templates", description: "Path to templates. File or Directory."),
Option<Path>("output", ".", description: "Path to output. File or Directory. Default is current path."),
Option<Path>("config", ".", description: "Path to config file. File or Directory. Default is current path."),
- VariadicOption<String>("parsableExtensions", description: "extensions that this run of Sorcery should parse"),
+ VariadicOption<String>("force-parse", description: "extensions that this run of Sorcery should parse"),
Argument<CustomArguments>("args", description: "Custom values to pass to templates.")
- ) { watcherEnabled, disableCache, verboseLogging, quiet, prune, sources, templates, output, configPath, parsableExtensions, args in
+ ) { watcherEnabled, disableCache, verboseLogging, quiet, prune, sources, templates, output, configPath, forceParse, args in
do {
Log.level = verboseLogging ? .verbose : quiet ? .errors : .info
@@ -139,7 +139,7 @@ func runCLI() {
configuration = Configuration(sources: sources,
templates: templates,
output: output,
- parsableExtensions: parsableExtensions,
+ forceParse: forceParse,
args: args.arguments)
} else {
_ = Validators.isFileOrDirectory(path: configPath)
@@ -167,7 +167,7 @@ func runCLI() {
configuration.source,
usingTemplates: configuration.templates,
output: configuration.output,
- parsableExtensions: configuration.parsableExtensions) {
+ forceParse: configuration.forceParse) {
RunLoop.current.run()
_ = keepAlive
} else {


message: Add tests for the `force-parse` feature

diff:
@@ -7,19 +7,31 @@ class VerifierSpec: QuickSpec {
override func spec() {
describe("Verifier") {
it("allows empty strings") {
- expect(Verifier.canParse(content: "")).to(equal(Verifier.Result.approved))
+ expect(Verifier.canParse(content: "", path: Path("/"))).to(equal(Verifier.Result.approved))
}
it("rejects files generated by Sourcery") {
let content = Sourcery.generationMarker + "\n something\n is\n there"
- expect(Verifier.canParse(content: content)).to(equal(Verifier.Result.isCodeGenerated))
+ expect(Verifier.canParse(content: content, path: Path("/"))).to(equal(Verifier.Result.isCodeGenerated))
+ }
+
+ it("rejects files generated by Sourcery when a force parse extension is defined but doesn't match file") {
+ let content = Sourcery.generationMarker + "\n something\n is\n there"
+
+ expect(Verifier.canParse(content: content, path: Path("/file.swift"), forceParse: ["toparse"])).to(equal(Verifier.Result.isCodeGenerated))
+ }
+
+ it("doesn't reject files generated by Soucery but that we want to force the parsing for") {
+ let content = Sourcery.generationMarker + "\n something\n is\n there"
+
+ expect(Verifier.canParse(content: content, path: Path("/file.toparse.swift"), forceParse: ["toparse"])).to(equal(Verifier.Result.approved))
}
it("rejects file containing conflict marker") {
let content = ["\n<<<<<\n", "\n>>>>>\n"]
- content.forEach { expect(Verifier.canParse(content: $0)).to(equal(Verifier.Result.containsConflictMarkers)) }
+ content.forEach { expect(Verifier.canParse(content: $0, path: Path("/"))).to(equal(Verifier.Result.containsConflictMarkers)) }
}
}
}

original_message: Add tests for the `force-parse` feature


message: Update README to explain --force-parse

diff:
@@ -96,6 +96,7 @@ $ ./sourcery --sources <sources path> --templates <templates path> --output <out
- `--sources` - Path to a source swift files. You can provide multiple paths using multiple `--sources` option.
- `--templates` - Path to templates. File or Directory. You can provide multiple paths using multiple `--templates` options.
+- `--force-parse` - File extensions of Sourcery generated file you want to parse. You can provide multiple extension using multiple `--force-parse` options. (i.e. file.toparse.swift will be parsed even if generated by Sourcery if `--force-parse toparse`). Useful when trying to implement a multiple phases generation.
- `--output` [default: current path] - Path to output. File or Directory.
- `--config` [default: current path] - Path to config file. Directory. See [Configuration file](#configuration-file).
- `--args` - Additional arguments to pass to templates. Each argument can have an explicit value or will have implicit `true` value. Arguments should be separated with `,` without spaces (i.e. `--args arg1=value,arg2`). Arguments are accessible in templates via `argument.name`
@@ -118,6 +119,9 @@ sources:
templates:
- <templates path>
- <templates path>
+force-parse:
+ - <string value>
+ - <string value>
output:
<output path>
args:

original_message: Update README to explain --force-parse


message: FIx filename format in the README.md

diff:
@@ -96,7 +96,7 @@ $ ./sourcery --sources <sources path> --templates <templates path> --output <out
- `--sources` - Path to a source swift files. You can provide multiple paths using multiple `--sources` option.
- `--templates` - Path to templates. File or Directory. You can provide multiple paths using multiple `--templates` options.
-- `--force-parse` - File extensions of Sourcery generated file you want to parse. You can provide multiple extension using multiple `--force-parse` options. (i.e. file.toparse.swift will be parsed even if generated by Sourcery if `--force-parse toparse`). Useful when trying to implement a multiple phases generation.
+- `--force-parse` - File extensions of Sourcery generated file you want to parse. You can provide multiple extension using multiple `--force-parse` options. (i.e. `file.toparse.swift` will be parsed even if generated by Sourcery if `--force-parse toparse`). Useful when trying to implement a multiple phases generation.
- `--output` [default: current path] - Path to output. File or Directory.
- `--config` [default: current path] - Path to config file. Directory. See [Configuration file](#configuration-file).
- `--args` - Additional arguments to pass to templates. Each argument can have an explicit value or will have implicit `true` value. Arguments should be separated with `,` without spaces (i.e. `--args arg1=value,arg2`). Arguments are accessible in templates via `argument.name`

original_message: FIx filename format in the README.md


message: Fix a link to `Types` documentation in Writing templates.md

old_path: guides/Writing templates.md

new_path: guides/Writing templates.md

diff:
@@ -4,7 +4,7 @@ Sourcery supports templates written in Stencil, Swift and even JavaScript.
Dicsovered types can be accessed in templates via global context with following properties:
- - `types: Types` - access collections of types, i.e. `types.implementing.AutoCoding` (`types.implementing["AutoCoding"]` in swift templates). See [Types](Classes/Types.html).
+ - `types: Types` - access collections of types, i.e. `types.implementing.AutoCoding` (`types.implementing["AutoCoding"]` in swift templates). See [Types](https://cdn.rawgit.com/krzysztofzablocki/Sourcery/master/docs/Classes/Types.html).
- `type: [String: Type]` - access types by their names, i.e. `type.MyType` (`type["MyType"]` in swift templates)
- `arguments: [String: NSObject]` - access additional parameters passed with `--args` command line flag or set in `.sourcery.yml` file


message:
Add a warning when the user is passing arguments but a yml config is used
If the user is specifing any parameter aside than config and a yml is used it shows a WARNING to avoid confusion

diff:
@@ -148,9 +148,25 @@ func runCLI() {
do {
let relativePath: Path = configPath.isDirectory ? configPath : configPath.parent()
configuration = try Configuration(path: yamlPath, relativePath: relativePath)
+
+ // Doesn't consider 'configPath' cause it's related to the yaml file
+ let hasAnyParameter = (watcherEnabled == true ||
+ disableCache == true ||
+ verboseLogging != true ||
+ quiet == true ||
+ prune == true ||
+ !sources.isEmpty ||
+ !templates.isEmpty ||
+ output != "" ||
+ !args.arguments.isEmpty)
+
+ if hasAnyParameter {
+ Log.info("Using configuration file at '\(yamlPath)'. WARNING: Ignoring the parameters passed in the command line.")
+ } else {
Log.info("Using configuration file at '\(yamlPath)'")
+ }
} catch {
- Log.error(error)
+ Log.error("while reading .yml '\(yamlPath)'. '\(error)'")
exit(.invalidConfig)
}
}


message:
Fix the check on the parameters
Check if the user is passing parameters that are ignored cause read from the yaml file.

diff:
@@ -149,16 +149,13 @@ func runCLI() {
let relativePath: Path = configPath.isDirectory ? configPath : configPath.parent()
configuration = try Configuration(path: yamlPath, relativePath: relativePath)
- // Doesn't consider 'configPath' cause it's related to the yaml file
- let hasAnyParameter = (watcherEnabled == true ||
- disableCache == true ||
- verboseLogging != true ||
- quiet == true ||
- prune == true ||
+ // Check if the user is passing parameters
+ // that are ignored cause read from the yaml file
+ let hasAnyParameter = (
!sources.isEmpty ||
!templates.isEmpty ||
- output != "" ||
- !args.arguments.isEmpty)
+ output != ""
+ )
if hasAnyParameter {
Log.info("Using configuration file at '\(yamlPath)'. WARNING: Ignoring the parameters passed in the command line.")


message:
Add parameter forceParse to the hasAnyParameter
After master rebase

diff:
@@ -154,6 +154,7 @@ func runCLI() {
let hasAnyParameter = (
!sources.isEmpty ||
!templates.isEmpty ||
+ !forceParse.isEmpty ||
output != ""
)


message: Fix the boolean var name as hasAnyYamlDuplicatedParameter

diff:
@@ -151,14 +151,14 @@ func runCLI() {
// Check if the user is passing parameters
// that are ignored cause read from the yaml file
- let hasAnyParameter = (
+ let hasAnyYamlDuplicatedParameter = (
!sources.isEmpty ||
!templates.isEmpty ||
!forceParse.isEmpty ||
output != ""
)
- if hasAnyParameter {
+ if hasAnyYamlDuplicatedParameter {
Log.info("Using configuration file at '\(yamlPath)'. WARNING: Ignoring the parameters passed in the command line.")
} else {
Log.info("Using configuration file at '\(yamlPath)'")


message:
Updates documentation on skipping cached launch event
For supporting subscribe call from async method, the timeout code was removed.
In order to give flexibility to the user in handling initial event, some changes are made to let user skip or use the cached event.

diff:
@@ -28,7 +28,7 @@ ___
3. Branch general methods
+ [Register a subscriber](#register-a-subscriber)
+ [Unregister a subscriber](#unregister-a-subscriber)
- + [Adjust cached link TTL](#adjust-cached-link-ttl)
+ + [Skip cached initial launch event](#skip-cached-initial-launch-event)
+ [Retrieve latest deep linking params](#retrieve-session-install-or-open-parameters)
+ [Retrieve the user's first deep linking params](#retrieve-install-install-only-parameters)
+ [Setting the user id for tracking influencers](#persistent-identities)
@@ -465,33 +465,41 @@ class MyApp extends Component {
___
-### Adjust cached link TTL
+### Skip cached initial launch event
Any initial link cached by the native layer will be returned to the callback
supplied to `branch.subscribe` immediately if the JavaScript method is called
-within a certain time from app launch. This allows components to unsubscribe and
-resubscribe without receiving the initial link after resubscription. By default,
-the TTL for any initial link is 5000 ms. To use a different value, set
-`branch.initSessionTtl` to that value, in ms. To have the desired effect, this
-parameter should be set at app launch.
+for the first time after app launch. In case, app does not need to receive
+the cached initial app launch link event, call `branch.skipCachedEvents()`
+before `branch.subscribe` to skip returning it.
-#### Property
+#### Method
```js
-branch.initSessionTtl
+branch.skipCachedEvents()
```
-An integer specifying the maximum amount of time, in milliseconds, to cache
-the initial link from app launch. Defaults to 5000.
+Also, if a cached initial app launch link event is returned, `params` will
+contain a key `cached_initial_event`, set to `true`.
#### Example
```js
import branch from 'react-native-branch'
-branch.initSessionTtl = 10000
+branch.skipCachedEvents()
+branch.subscribe({ error, params } => {
+ // ...
+})
+```
+
+```js
+import branch from 'react-native-branch'
+
branch.subscribe({ error, params } => {
+ if ('cached_initial_event' in params) {
// ...
+ }
})
```


message:
Fixes the issue in creating custom branch event.
Efficiently detects whether standard event or custom event and uses BranchEvent to build corresponding event instance.

diff:
@@ -540,13 +540,14 @@ public class RNBranchModule extends ReactContextBaseJavaModule {
}
public static BranchEvent createBranchEvent(String eventName, ReadableMap params) {
- BRANCH_STANDARD_EVENT standardEvent = BRANCH_STANDARD_EVENT.valueOf(eventName);
BranchEvent event;
-
- if (standardEvent != null) {
+ try {
+ BRANCH_STANDARD_EVENT standardEvent = BRANCH_STANDARD_EVENT.valueOf(eventName);
+ // valueOf on BRANCH_STANDARD_EVENT Enum has succeeded, so this is a standard event.
event = new BranchEvent(standardEvent);
- }
- else {
+ } catch (IllegalArgumentException e) {
+ // The event name is not found in standard events.
+ // So use custom event mode.
event = new BranchEvent(eventName);
}

original_message:
Fixes the issue in creating custom branch event.

- Efficiently detects whether standard event or custom event and uses BranchEvent to build corresponding event instance.


message: Automatic unit tests for new subscribe to cached event logic

diff:
@@ -10,12 +10,11 @@ import branch, {
PurchaseInitiatedEvent,
RegisterViewEvent,
ShareCompletedEvent,
- ShareInitiatedEvent,
- DEFAULT_INIT_SESSION_TTL
+ ShareInitiatedEvent
} from '../src/index.js'
test.beforeEach(() => {
- branch.initSessionTtl = DEFAULT_INIT_SESSION_TTL
+ branch._checkCachedEvents = true
})
test.afterEach(() => {
@@ -30,24 +29,50 @@ test('subscribe returns init session', t => {
})
})
-test('subscribe does not return init session beyond the TTL', t => {
- branch.initSessionTtl = 0 // disable the cache check in this test
+test('subscribe returns cached init session with cached_initial_event flag', t => {
+ mock(RNBranch, 'redeemInitSessionResult', () => {
+ return new Promise((resolve, reject) => {
+ resolve({error: null, params: {}, uri: 'test'})
+ })
+ })
+
+ return new Promise((resolve, reject) => {
+ let event_received = false
+ branch.subscribe(({ error, params, uri }) => {
+ event_received = true
+ if(params.cached_initial_event)
+ resolve()
+ else
+ reject('cached_initial_event is not set')
+ })
+
+ setTimeout(() => {
+ if(!event_received)
+ reject('event not received')
+ }, 100)
+ })
+})
+test('subscribe does not call redeemInitSessionResult if skipCachedEvents is called', t => {
+ mock(RNBranch, 'redeemInitSessionResult', () => {
return new Promise((resolve, reject) => {
- let listenerWasCalled = false
+ resolve({error: null, params: null, uri: null})
+ })
+ })
+
+ return new Promise((resolve, reject) => {
+ branch.skipCachedEvents()
branch.subscribe(({ error, params, uri }) => {
- listenerWasCalled = true
- reject('subscribe listener should not be called')
+ reject('cached initial session event returned')
})
setTimeout(() => {
- if (!listenerWasCalled) resolve()
- // else reject() was already called
+ resolve()
}, 100)
})
})
-test.serial('subscribe does not add an event listener within the TTL until redeemInitSession returns', t => {
+test.serial('subscribe does not add an event listener before redeemInitSession returns', t => {
/*
* Desperately want a mock framework for this
*/
@@ -63,7 +88,7 @@ test.serial('subscribe does not add an event listener within the TTL until redee
return new Promise((resolve, reject) => {
setTimeout(() => {
t.false(this.listenerAdded)
- resolve({error: null, params: null, uri: null})
+ resolve({error: null, params: {}, uri: null})
}, 100)
})
})
@@ -77,7 +102,7 @@ test.serial('subscribe does not add an event listener within the TTL until redee
})
})
-test.serial('after the TTL, subscribe adds a listener and does not call redeemInitSessionResult', t => {
+test.serial('once skipCachedEvents is called, subscribe adds a listener and does not call redeemInitSessionResult', t => {
/*
* Desperately want a mock framework for this
*/
@@ -96,7 +121,7 @@ test.serial('after the TTL, subscribe adds a listener and does not call redeemIn
})
})
- branch.initSessionTtl = 0 // disable the cache check in this test
+ branch.skipCachedEvents() // disable the cache check in this test
return new Promise((resolve, reject) => {
branch.subscribe(({error, params, uri}) => {})


message: Fixes eslint formatting issues

diff:
@@ -36,7 +36,7 @@ class Branch {
* If none, the listener is not called. If there is a cached value, it is passed to the listener.
*/
if (this._checkCachedEvents) {
- this._checkCachedEvents = false;
+ this._checkCachedEvents = false
RNBranch.redeemInitSessionResult().then((result) => {
if (result) {

original_message: Fixes eslint formatting issues


message: Clears cached latest session params when iOS application becomes inactive

diff:
@@ -1820,6 +1820,7 @@ static BOOL bnc_enableFingerprintIDInCrashlyticsReports = YES;
}
- (void)applicationWillResignActive {
+ [self clearSessionParams];
[self callClose];
[self.requestQueue persistImmediately];
[BranchOpenRequest setWaitNeededForOpenResponseLock];
@@ -1846,6 +1847,11 @@ static BOOL bnc_enableFingerprintIDInCrashlyticsReports = YES;
}
}
+- (void)clearSessionParams {
+ // Reset session params
+ self.preferenceHelper.sessionParams = nil;
+}
+
#pragma mark - Queue management
- (NSInteger) networkCount {


message: Integrates tracking disabling feature for GDPR compliance

diff:
@@ -295,6 +295,18 @@ public class RNBranchModule extends ReactContextBaseJavaModule {
return REACT_MODULE_NAME;
}
+ @ReactMethod
+ public void disableTracking(boolean disable) {
+ Branch branch = Branch.getInstance();
+ branch.disableTracking(disable);
+ }
+
+ @ReactMethod
+ public void isTrackingDisabled(Promise promise) {
+ Branch branch = Branch.getInstance();
+ promise.resolve(branch.isTrackingDisabled());
+ }
+
@ReactMethod
public void createUniversalObject(ReadableMap universalObjectMap, Promise promise) {
String ident = UUID.randomUUID().toString();

diff:
@@ -276,6 +276,21 @@ RCT_EXPORT_MODULE();
#pragma mark - Methods exported to React Native
+#pragma mark disableTracking
+RCT_EXPORT_METHOD(
+ disableTracking:(BOOL)disable
+ ) {
+ [Branch setTrackingDisabled: disable];
+}
+
+#pragma mark isTrackingDisabled
+RCT_EXPORT_METHOD(
+ isTrackingDisabled:(RCTPromiseResolveBlock)resolve
+ rejecter:(__unused RCTPromiseRejectBlock)reject
+ ) {
+ resolve([Branch trackingDisabled] ? @YES : @NO);
+}
+
#pragma mark redeemInitSessionResult
RCT_EXPORT_METHOD(
redeemInitSessionResult:(RCTPromiseResolveBlock)resolve

diff:
@@ -95,6 +95,10 @@ class Branch {
this.nativeEventEmitter.removeListener(RNBranch.INIT_SESSION_ERROR, listener)
}
+ /*** Tracking related methods ***/
+ disableTracking = (disable) => RNBranch.disableTracking(disable)
+ isTrackingDisabled = RNBranch.isTrackingDisabled
+
/*** RNBranch singleton methods ***/
setDebug = () => { throw 'setDebug() is not supported in the RN SDK. For other solutions, please see https://rnbranch.app.link/setDebug' }
getLatestReferringParams = RNBranch.getLatestReferringParams


message: Tests user tracking enable/disable api

diff:
import React from 'react-native'
const defaultSession = {params: {}, error: null}
+let tackingDisabled = false
React.NativeModules.RNBranch = {
// Mock constants exported by native layers
@@ -35,7 +36,18 @@ React.NativeModules.RNBranch = {
return new Promise((resolve, reject) => {
setTimeout(() => resolve(defaultSession), 500)
})
- }
+ },
+
+ disableTracking(disable) {
+ // Just mock user tracking enable/disable
+ tackingDisabled = disable
+ },
+
+ isTrackingDisabled() {
+ return new Promise((resolve, reject) => {
+ resolve(tackingDisabled)
+ })
+ },
}
// This only has to exist to be passed to the NativeEventEmitter

diff:
@@ -21,6 +21,26 @@ test.afterEach(() => {
unmock()
})
+test('can disable user tracking', t => {
+ return new Promise((resolve, reject) => {
+ branch.disableTracking(true)
+ branch.isTrackingDisabled().then((disabled) => {
+ if(disabled) resolve()
+ else reject('Could not disable user tracking')
+ })
+ })
+})
+
+test('can enable user tracking', t => {
+ return new Promise((resolve, reject) => {
+ branch.disableTracking(false)
+ branch.isTrackingDisabled().then((disabled) => {
+ if(!disabled) resolve()
+ else reject('Could not enable user tracking')
+ })
+ })
+})
+
test('subscribe returns init session', t => {
return new Promise((resolve, reject) => {
branch.subscribe((session) => {

original_message: Tests user tracking enable/disable api


message: User tracking related APIs added to testbaed_simple

diff:
@@ -128,6 +128,32 @@ class BranchMethods extends Component {
}
}
+ disableTracking = async () => {
+ try {
+ let disabled = await branch.isTrackingDisabled()
+ branch.disableTracking(!disabled)
+ disabled = await branch.isTrackingDisabled()
+ let status = disabled ? 'Tracking Disabled' : 'Tracking Enabled'
+ console.log('disableTracking', status)
+ this.addResult('success', 'disableTracking', status)
+ } catch (err) {
+ console.log('disableTracking err', err)
+ this.addResult('error', 'disableTracking', err.toString())
+ }
+ }
+
+ isTrackingDisabled = async () => {
+ try {
+ let disabled = await branch.isTrackingDisabled()
+ let status = disabled ? 'Tracking is Disabled' : 'Tracking is Enabled'
+ console.log('isTrackingDisabled', status)
+ this.addResult('success', 'isTrackingDisabled', status)
+ } catch (err) {
+ console.log('isTrackingDisabled err', err)
+ this.addResult('error', 'isTrackingDisabled', err.toString())
+ }
+ }
+
addResult(type, slug, payload) {
let result = { type, slug, payload }
this.setState({
@@ -154,6 +180,8 @@ class BranchMethods extends Component {
</View>
<Text style={styles.header}>METHODS</Text>
<ScrollView style={styles.buttonsContainer}>
+ <Button onPress={this.disableTracking}>disableTracking (switch on/off)</Button>
+ <Button onPress={this.isTrackingDisabled}>isTrackingDisabled</Button>
<Button onPress={this.createBranchUniversalObject}>createBranchUniversalObject</Button>
<Button onPress={this.userCompletedAction}>userCompletedAction</Button>
<Button onPress={this.sendCommerceEvent}>sendCommerceEvent</Button>


message: Fix event param settings

diff:
@@ -579,9 +579,9 @@ public class RNBranchModule extends ReactContextBaseJavaModule {
if (params.hasKey("shipping")) event.setShipping(Double.parseDouble(params.getString("shipping")));
if (params.hasKey("tax")) event.setTax(Double.parseDouble(params.getString("tax")));
if (params.hasKey("coupon")) event.setCoupon(params.getString("coupon"));
- if (params.hasKey("affiliation")) event.setTransactionID(params.getString("affiliation"));
- if (params.hasKey("description")) event.setTransactionID(params.getString("description"));
- if (params.hasKey("searchQuery")) event.setTransactionID(params.getString("searchQuery"));
+ if (params.hasKey("affiliation")) event.setAffiliation(params.getString("affiliation"));
+ if (params.hasKey("description")) event.setDescription(params.getString("description"));
+ if (params.hasKey("searchQuery")) event.setSearchQuery(params.getString("searchQuery"));
if (params.hasKey("customData")) {
ReadableMap customData = params.getMap("customData");

original_message: Fix event param settings


message: Test standard and custom branch event in testbed example

diff:
@@ -3,7 +3,7 @@ import { ScrollView, StyleSheet, Text, View } from 'react-native'
import Button from './Button'
-import branch, { RegisterViewEvent } from 'react-native-branch'
+import branch, { RegisterViewEvent, BranchEvent } from 'react-native-branch'
const defaultBUO = {
title: 'wallo'
@@ -154,6 +154,68 @@ class BranchMethods extends Component {
}
}
+ logStandardEvent = async () => {
+ if (!this.buo) await this.createBranchUniversalObject()
+ try {
+ let branchEvent = new BranchEvent(
+ BranchEvent.Purchase,
+ this.buo,
+ {
+ transactionID: '12344555',
+ currency: 'USD',
+ revenue: 1.5,
+ shipping: 10.2,
+ tax: 12.3,
+ coupon: 'test_coupon',
+ affiliation: 'test_affiliation',
+ description: 'Test purchase event',
+ searchQuery: 'test keyword',
+ customData: {
+ "Custom_Event_Property_Key1": "Custom_Event_Property_val1",
+ "Custom_Event_Property_Key2": "Custom_Event_Property_val2"
+ }
+ }
+ )
+ branchEvent.logEvent()
+
+ this.addResult('success', 'sendStandardEvent', branchEvent)
+ } catch (err) {
+ console.log('sendStandardEvent err', err)
+ this.addResult('error', 'sendStandardEvent', err.toString())
+ }
+ }
+
+ logCustomEvent = async () => {
+ if (!this.buo) await this.createBranchUniversalObject()
+ try {
+ let branchEvent = new BranchEvent(
+ 'Test Custom Event Name',
+ this.buo,
+ {
+ transactionID: '12344555',
+ currency: 'USD',
+ revenue: 1.5,
+ shipping: 10.2,
+ tax: 12.3,
+ coupon: 'test_coupon',
+ affiliation: 'test_affiliation',
+ description: 'Test purchase event',
+ searchQuery: 'test keyword',
+ customData: {
+ "Custom_Event_Property_Key1": "Custom_Event_Property_val1",
+ "Custom_Event_Property_Key2": "Custom_Event_Property_val2"
+ }
+ }
+ )
+ branchEvent.logEvent()
+
+ this.addResult('success', 'sendStandardEvent', branchEvent)
+ } catch (err) {
+ console.log('sendStandardEvent err', err)
+ this.addResult('error', 'sendStandardEvent', err.toString())
+ }
+ }
+
addResult(type, slug, payload) {
let result = { type, slug, payload }
this.setState({
@@ -192,6 +254,8 @@ class BranchMethods extends Component {
<Button onPress={this.redeemRewards.bind(this, 'testBucket')}>redeemRewards (with bucket)</Button>
<Button onPress={this.loadRewards}>loadRewards</Button>
<Button onPress={this.getCreditHistory}>getCreditHistory</Button>
+ <Button onPress={this.logStandardEvent}>BranchEvent.logEvent (Standard)</Button>
+ <Button onPress={this.logCustomEvent}>BranchEvent.logEvent (Custom)</Button>
</ScrollView>
</View>
)


message: docs: Add missing commands needed for a fresh installation

diff:
@@ -27,6 +27,7 @@ Clone this repository and build the lokoctl binary:
```bash
git clone https://github.com/kinvolk/lokoctl $GOPATH/src/github.com/kinvolk/lokoctl
cd $_
+make install-packr2
make
```

diff:
@@ -9,9 +9,10 @@ This guide walks through the installation of Lokomotive on AWS.
* [Terraform v0.11.x](https://www.terraform.io/downloads.html)
* [Terraform-provider-ct](https://github.com/coreos/terraform-provider-ct) installed locally
```bash
- wget https://github.com/coreos/terraform-provider-ct/releases/download/v0.3.0/terraform-provider-ct-v0.3.0-linux-amd64.tar.gz
- tar xzf terraform-provider-ct-v0.3.0-linux-amd64.tar.gz
- mv terraform-provider-ct-v0.3.0-linux-amd64/terraform-provider-ct ~/.terraform.d/plugins/terraform-provider-ct_v0.3.0
+ wget https://github.com/poseidon/terraform-provider-ct/releases/download/v0.3.1/terraform-provider-ct-v0.3.1-linux-amd64.tar.gz
+ tar xzf terraform-provider-ct-v0.3.1-linux-amd64.tar.gz
+ mkdir -p ~/.terraform.d/plugins
+ mv terraform-provider-ct-v0.3.1-linux-amd64/terraform-provider-ct ~/.terraform.d/plugins/terraform-provider-ct_v0.3.1
```
## Installing the cluster

license: Apache License 2.0

repo: kinvolk/lokomotive


old_path: lokomotive-kubernetes

new_path: lokomotive-kubernetes

diff:
-Subproject commit 52cd6dd25dbcc2f17993ec7c82399a8ec22c23c0
+Subproject commit fbb7eee2cd38a81019ed292a8a5eb6ae05328957

diff:
@@ -30,7 +30,6 @@ module "packet-{{.Config.ClusterName}}" {
worker_count = "{{ .WorkerCount }}"
worker_nodes_hostnames = "${concat({{- range $index, $pool := .Config.WorkerPools }}{{- if $index }}, {{- end }}"${module.worker-pool-{{- $index }}.worker_nodes_hostname}"{{- end }})}"
- worker_nodes_public_ipv4s = "${concat({{- range $index, $pool := .Config.WorkerPools }}{{- if $index }}, {{- end }}"${module.worker-pool-{{- $index }}.worker_nodes_public_ipv4}"{{- end }})}"
{{- if .Config.IPXEScriptURL }}
ipxe_script_url = "{{ .Config.IPXEScriptURL }}"

original_message:
Update lokomotive and remove worker_nodes_public_ipv4s

Use latest master in the lokomotive-kubernetes submodule.
This required removing the worker_nodes_public_ipv4s
argument which got deleted in
https://github.com/kinvolk/lokomotive-kubernetes/commit/e65be3ff5de61a60705281cee5bcddfdf92044b4


message:
libvirt: Reference current bootkube module
This updates the bootkube reference to the
current master state.
The previously referenced bootkube PR commit
got merged, so the comment is not needed anymore.

diff:
module "bootkube" {
- source = "git::https://github.com/kinvolk/terraform-render-bootkube//?ref=0d3951ff08eeaa617e5aa83da5db19ef2e12324f" # currently in PR #12
+ source = "git::https://github.com/kinvolk/terraform-render-bootkube//?ref=7c30aca430ea17c8a377d47b83a783428dc8a2a4"
cluster_name = "${var.cluster_name}"


message:
Preserve environment variables when executing terraform
Only inheriting specific environment variables to terraform
means that passing credentials as environment variables does
not work.
Instead of whitelisting all possible variables, just give
terraform, and thus the providers, full access to all
current environment variables.

diff:
@@ -170,24 +170,7 @@ func (ex *Executor) AddCredentials(credentials *Credentials) error {
// TerraForm call itself failed, in which case, details can be found in the
// output.
func (ex *Executor) Execute(args ...string) (int, chan struct{}, error) {
- // Prepare TerraForm command by setting up the command, configuration,
- // working directory (so the files such as terraform.tfstate are stored at
- // the right place), extra environment variables and outputs.
- cmd := exec.Command(ex.binaryPath, args...)
- // ssh changes its behavior based on these. pass them through so ssh-agent & stuff works
- cmd.Env = append(cmd.Env, fmt.Sprintf("DISPLAY=%s", os.Getenv("DISPLAY")))
- cmd.Env = append(cmd.Env, fmt.Sprintf("PATH=%s", os.Getenv("PATH")))
- cmd.Env = append(cmd.Env, fmt.Sprintf("HOME=%s", os.Getenv("HOME")))
- for _, v := range os.Environ() {
- if strings.HasPrefix(v, "SSH_") {
- cmd.Env = append(cmd.Env, v)
- }
- }
- for k, v := range ex.envVariables {
- cmd.Env = append(cmd.Env, fmt.Sprintf("%s=%s", strings.ToUpper(k), v))
- }
- cmd.Dir = ex.executionPath
-
+ cmd := ex.generateCommand(args...)
rPipe, wPipe := io.Pipe()
cmd.Stdout = wPipe
cmd.Stderr = wPipe
@@ -224,25 +207,25 @@ func (ex *Executor) Execute(args ...string) (int, chan struct{}, error) {
return cmd.Process.Pid, done, nil
}
-// ExecuteSync Like Execute, but synchronous. TODO: clean up duplicate cmd setup code
+// ExecuteSync is like Execute, but synchronous.
func (ex *Executor) ExecuteSync(args ...string) ([]byte, error) {
- // Prepare TerraForm command by setting up the command, configuration,
- // working directory (so the files such as terraform.tfstate are stored at
- // the right place), extra environment variables and outputs.
- cmd := exec.Command(ex.binaryPath, args...)
- // ssh changes its behavior based on these. pass them through so ssh-agent & stuff works
- cmd.Env = append(cmd.Env, fmt.Sprintf("DISPLAY=%s", os.Getenv("DISPLAY")))
- cmd.Env = append(cmd.Env, fmt.Sprintf("PATH=%s", os.Getenv("PATH")))
- for _, v := range os.Environ() {
- if strings.HasPrefix(v, "SSH_") {
- cmd.Env = append(cmd.Env, v)
- }
+ cmd := ex.generateCommand(args...)
+ return cmd.Output()
}
+
+// GenerateCommand prepares a Terraform command with the given arguments
+// by setting up the command, configuration, working directory
+// (so the files such as terraform.tfstate are stored at the right place) and
+// extra environment variables. The current environment is fully inherited.
+func (ex *Executor) generateCommand(args ...string) *exec.Cmd {
+ cmd := exec.Command(ex.binaryPath, args...)
+ // Copy environment because nil cannot be used to inherit if we add something in the next step.
+ cmd.Env = os.Environ()
for k, v := range ex.envVariables {
cmd.Env = append(cmd.Env, fmt.Sprintf("%s=%s", strings.ToUpper(k), v))
}
cmd.Dir = ex.executionPath
- return cmd.Output()
+ return cmd
}
// WorkingDirectory returns the directory in which TerraForm runs, which can be


message:
docs: Use cluster-specific asset path in example
The asset directory was just called after lokoctl.
Since the asset directory has to be unique per
project, this would not work and has to be
customized.
Use the cluster name as example directory name to
make it clear that this changes per cluster.

diff:
@@ -73,7 +73,7 @@ The maximal length for a cluster name is 18 characters.
Create a `lokocfg.vars` file and define all needed variables. Example:
```
-asset_dir = "~/lokoctl-assets"
+asset_dir = "~/lokoctl-assets/mycluster"
#aws_creds = "~/.aws/credentials"
ssh_pubkey = "~/.ssh/id_rsa.pub"
```


message:
Trigger copying of secrets when recreating nodes
Recreating a deleted controller node through terraform
apply resulted in a broken etcd because the secrets were
not provisioned.

diff:
@@ -58,6 +58,10 @@ resource "null_resource" "copy-controller-secrets" {
"sudo chmod -R 500 /etc/ssl/etcd",
]
}
+
+ triggers = {
+ controller_id = "${aws_instance.controllers.*.id[count.index]}"
+ }
}
# Secure copy bootkube assets to ONE controller and start bootkube to perform

diff:
@@ -58,6 +58,10 @@ resource "null_resource" "copy-controller-secrets" {
"sudo chmod -R 500 /etc/ssl/etcd",
]
}
+
+ triggers = {
+ controller_id = "${packet_device.controllers.*.id[count.index]}"
+ }
}
# Secure copy bootkube assets to ONE controller and start bootkube to perform


message: Refactor cluster install to use getConfiguredPlatform()

diff:
@@ -13,7 +13,6 @@ import (
"github.com/kinvolk/lokoctl/pkg/install"
"github.com/kinvolk/lokoctl/pkg/k8sutil"
"github.com/kinvolk/lokoctl/pkg/lokomotive"
- "github.com/kinvolk/lokoctl/pkg/platform"
)
var clusterCmd = &cobra.Command{
@@ -43,30 +42,22 @@ func runClusterInstall(cmd *cobra.Command, args []string) {
ctxLogger.Fatal(diags)
}
- if lokoConfig.RootConfig.Cluster == nil {
- ctxLogger.Fatal("No cluster configured")
- }
-
- clusterType := lokoConfig.RootConfig.Cluster.Name
- clusterConfigBody := &lokoConfig.RootConfig.Cluster.Config
-
- var assetDir string
-
- p, err := platform.GetPlatform(clusterType)
- if err != nil {
- ctxLogger.Fatal(err)
- }
- if diags := p.LoadConfig(clusterConfigBody, lokoConfig.EvalContext); diags.HasErrors() {
+ p, diags := getConfiguredPlatform(lokoConfig)
+ if diags.HasErrors() {
for _, diagnostic := range diags {
ctxLogger.Error(diagnostic.Summary)
}
ctxLogger.Fatal("Errors found while loading cluster configuration")
}
+ if p == nil {
+ ctxLogger.Fatal("No cluster configured")
+ }
+
if err := p.Install(); err != nil {
ctxLogger.Fatalf("error installing cluster: %v", err)
}
- assetDir = p.GetAssetDir()
+ assetDir := p.GetAssetDir()
fmt.Printf("\nYour configurations are stored in %s\n", assetDir)


message: docs: Node recreation and restoring an etcd cluster

diff:
# Maintenance
+## Emergency handling
+
+When a node failed and cannot be restarted, delete it completely and recreate it by running Terraform:
+
+```
+terraform apply
+```
+
+This works for both worker and controller nodes. Exactly the same
+configuration and Lokomotive revision should be used because otherwise
+not only the deleted node will be recreated but others might be as well.
+
+However, when more than half of the controller nodes failed,
+etcd lost its quorum and is in read-only mode. Recreated nodes
+are not able to join as new members anymore. Read up how to
+manually restore the etcd cluster starting a new etcd cluster
+from a single etcd member with backup data. The alternative is
+creating an empty new Kubernetes cluster and migrating your
+applications to the new cluster.
+
## Best Practices
* Run multiple Kubernetes clusters. Run across platforms. Plan for regional and cloud outages.


message:
Update the libvirt platform to use fw_cfg_name
Recently Flatcar changed to expect the ignition
config under the name org.flatcar-linux.
The terraform libvirt provider has a commit in
its master branch that allows to set this option.
Use it and document how to build from source for
the mean time.

diff:
@@ -87,19 +87,28 @@ mv terraform-provider-ct-v0.3.1-linux-amd64/terraform-provider-ct ~/.terraform.d
```
Add the [terraform-provider-libvirt](https://github.com/dmacvicar/terraform-provider-libvirt) plugin binary for your system
-to `~/.terraform.d/plugins/`, noting the `_v0.5.2` suffix.
-You should use the 0.5.2 release because it includes the experimental
-[pool definition feature](https://github.com/dmacvicar/terraform-provider-libvirt/commit/9f00f3d46c489f24c71d02fde816f5fda34d3f7c)
-but building from source is also an option.
+to `~/.terraform.d/plugins/`, noting the `_v0.5.3` suffix. As long as this version is unreleased you have to build the plugin
+yourself. The version must include the `fw_cfg_name` feature as well as the experimental
+[pool definition feature](https://github.com/dmacvicar/terraform-provider-libvirt/commit/9f00f3d46c489f24c71d02fde816f5fda34d3f7c).
-Use the tar file for your distribution when you download it from the [release page](https://github.com/dmacvicar/terraform-provider-libvirt/releases).
-When building from source, you would do a final `mv $GOPATH/bin/terraform-provider-libvirt ~/.terraform.d/plugins/terraform-provider-libvirt_v0.5.2`.
+When building from source, you have to do a final `mv $GOPATH/bin/terraform-provider-libvirt ~/.terraform.d/plugins/terraform-provider-libvirt_v0.5.3`:
```sh
-wget https://github.com/dmacvicar/terraform-provider-libvirt/releases/download/v0.5.2/terraform-provider-libvirt-0.5.2.Fedora_28.x86_64.tar.gz
-# or, e.g., https://github.com/dmacvicar/terraform-provider-libvirt/releases/download/v0.5.2/terraform-provider-libvirt-0.5.2.Ubuntu_18.04.amd64.tar.gz
-tar xzf terraform-provider-libvirt-0.5.2.Fedora_28.x86_64.tar.gz
-mv terraform-provider-libvirt ~/.terraform.d/plugins/terraform-provider-libvirt_v0.5.2
+$ # From any (even temporary) directory
+$ git clone https://github.com/dmacvicar/terraform-provider-libvirt.git
+$ export GO111MODULE=on
+$ export GOFLAGS=-mod=vendor
+$ make install
+$ mv "$GOPATH/bin/terraform-provider-libvirt" ~/.terraform.d/plugins/terraform-provider-libvirt_v0.5.3
+```
+
+In the future you can download the tar file for your distribution from the [release page](https://github.com/dmacvicar/terraform-provider-libvirt/releases):
+
+```sh
+wget https://github.com/dmacvicar/terraform-provider-libvirt/releases/download/v0.5.3/terraform-provider-libvirt-0.5.3.Fedora_28.x86_64.tar.gz
+# or, e.g., https://github.com/dmacvicar/terraform-provider-libvirt/releases/download/v0.5.2/terraform-provider-libvirt-0.5.3.Ubuntu_18.04.amd64.tar.gz
+tar xzf terraform-provider-libvirt-0.5.3.Fedora_28.x86_64.tar.gz
+mv terraform-provider-libvirt ~/.terraform.d/plugins/terraform-provider-libvirt_v0.5.3
```
@@ -143,7 +152,7 @@ provider "tls" {
}
provider "libvirt" {
- version = "~> 0.5.2"
+ version = "~> 0.5.3"
uri = "qemu:///system"
alias = "default"
}

diff:
@@ -49,6 +49,7 @@ resource "libvirt_domain" "controller-machine" {
vcpu = "${var.virtual_cpus}"
memory = "${var.virtual_memory}"
+ fw_cfg_name = "opt/org.flatcar-linux/config"
coreos_ignition = "${element(libvirt_ignition.ignition.*.id, count.index)}"
disk {

diff:
@@ -25,7 +25,7 @@ provider "tls" {
}
provider "libvirt" {
- version = "~> 0.5.2"
+ version = "~> 0.5.3"
uri = "qemu:///system"
}

diff:
@@ -21,7 +21,7 @@ provider "tls" {
}
provider "libvirt" {
- version = "~> 0.5.2"
+ version = "~> 0.5.3"
uri = "qemu:///system"
}

diff:
@@ -19,6 +19,7 @@ resource "libvirt_domain" "worker-machine" {
vcpu = "${var.virtual_cpus}"
memory = "${var.virtual_memory}"
+ fw_cfg_name = "opt/org.flatcar-linux/config"
coreos_ignition = "${element(libvirt_ignition.ignition.*.id, count.index)}"
disk {


message:
Fix Kubelet cgroup driver selection
The selection mechanism was broken so that
the Kubelet always chose the default cgroup driver
which broke Flatcar Edge.

diff:
@@ -213,7 +213,7 @@ storage:
inline: |
#!/bin/bash
set -e
- readonly docker_cgroup_driver="$(docker info | awk '/^Cgroup Driver/ { print $3 }')"
+ readonly docker_cgroup_driver="$(docker info -f '{{.CgroupDriver}}')"
cat <<EOF >/etc/kubernetes/kubelet.config
apiVersion: kubelet.config.k8s.io/v1beta1
kind: KubeletConfiguration

diff:
@@ -113,7 +113,7 @@ storage:
inline: |
#!/bin/bash
set -e
- readonly docker_cgroup_driver="$(docker info | awk '/^Cgroup Driver/ { print $3 }')"
+ readonly docker_cgroup_driver="$(docker info -f '{{.CgroupDriver}}')"
cat <<EOF >/etc/kubernetes/kubelet.config
apiVersion: kubelet.config.k8s.io/v1beta1
kind: KubeletConfiguration

diff:
@@ -170,7 +170,7 @@ storage:
inline: |
#!/bin/bash
set -e
- readonly docker_cgroup_driver="$(docker info | awk '/^Cgroup Driver/ { print $3 }')"
+ readonly docker_cgroup_driver="$(docker info -f '{{.CgroupDriver}}')"
cat <<EOF >/etc/kubernetes/kubelet.config
apiVersion: kubelet.config.k8s.io/v1beta1
kind: KubeletConfiguration

diff:
@@ -120,7 +120,7 @@ storage:
inline: |
#!/bin/bash
set -e
- readonly docker_cgroup_driver="$(docker info | awk '/^Cgroup Driver/ { print $3 }')"
+ readonly docker_cgroup_driver="$(docker info -f '{{.CgroupDriver}}')"
cat <<EOF >/etc/kubernetes/kubelet.config
apiVersion: kubelet.config.k8s.io/v1beta1
kind: KubeletConfiguration

diff:
@@ -267,7 +267,7 @@ storage:
inline: |
#!/bin/bash
set -e
- readonly docker_cgroup_driver="$(docker info | awk '/^Cgroup Driver/ { print $3 }')"
+ readonly docker_cgroup_driver="$(docker info -f '{{.CgroupDriver}}')"
cat <<EOF >/etc/kubernetes/kubelet.config
apiVersion: kubelet.config.k8s.io/v1beta1
kind: KubeletConfiguration

diff:
@@ -291,7 +291,7 @@ storage:
inline: |
#!/bin/bash
set -e
- readonly docker_cgroup_driver="$(docker info | awk '/^Cgroup Driver/ { print $3 }')"
+ readonly docker_cgroup_driver="$(docker info -f '{{.CgroupDriver}}')"
cat <<EOF >/etc/kubernetes/kubelet.config
apiVersion: kubelet.config.k8s.io/v1beta1
kind: KubeletConfiguration


message: Document changeable Packet variables

diff:
@@ -326,3 +326,13 @@ Reference the DNS zone id with `"${aws_route53_zone.zone-for-clusters.zone_id}"`
| reservation_ids_default | Default hardware reservation ID for nodes not listed in the `reservation_ids` map. | "" | "next-available"|
Documentation about Packet hardware reservation id can be found here: https://support.packet.com/kb/articles/reserved-hardware.
+
+## Post-installation modification
+
+Currently the only tested ways to modify a cluster are:
+
+* Adding new worker pools, done by adding a new worker module and concating its `.worker_nodes_hostname` to the `worker_nodes_hostnames` list of the controller module.
+* Scaling a worker pool by changing the `count` to delete or add nodes, even to 0 (but the worker pool definition has to be kept and the total number of workers must be > 0).
+* Changing the instance type of a worker pool by altering `type`, e.g., from `t1.small.x86` to `c1.small.x86`, which will recreate the nodes, causing downtime since they are destroyed first and then created again.
+
+This list may be expanded in the future but for now other changes are not supported but can be done at your own risk.

diff:
@@ -19,13 +19,13 @@ variable "pool_name" {
variable "count" {
type = "string"
default = "1"
- description = "Number of workers"
+ description = "Number of workers, can be changed afterwards to delete or add nodes"
}
variable "type" {
type = "string"
default = "baremetal_0"
- description = "Packet instance type for workers"
+ description = "Packet instance type for workers, can be changed afterwards to recreate the nodes"
}
# TODO: migrate to `templatefile` when Terraform `0.12` is out and use `{% for ~}`

original_message: Document changeable Packet variables


message: Update copyright information

old_path: LICENSE

new_path: LICENSE

diff:
The MIT License (MIT)
+Copyright (c) 2019 Kinvolk GmbH
Copyright (c) 2017 Typhoon Authors
Copyright (c) 2017 Dalton Hubble

old_path: aws/flatcar-linux/kubernetes/LICENSE

new_path: aws/flatcar-linux/kubernetes/LICENSE

old_path: azure/flatcar-linux/kubernetes/LICENSE

new_path: azure/flatcar-linux/kubernetes/LICENSE

original_message: Update copyright information


message:
Link from terraform module readme to global readme
The contents were outdated or inconsistent. Instead of keeping them
in sync just link to the repository readme.

diff:
-# Typhoon
-
-Typhoon is a minimal and free Kubernetes distribution.
-
-* Minimal, stable base Kubernetes distribution
-* Declarative infrastructure and configuration
-* Free (freedom and cost) and privacy-respecting
-* Practical for labs, datacenters, and clouds
-
-Typhoon distributes upstream Kubernetes, architectural conventions, and cluster addons, much like a GNU/Linux distribution provides the Linux kernel and userspace components.
-
-## Features
-
-* Kubernetes v1.14.1 (upstream, via [kubernetes-incubator/bootkube](https://github.com/kubernetes-incubator/bootkube))
-* Single or multi-master, [Calico](https://www.projectcalico.org/) or [flannel](https://github.com/coreos/flannel) networking
-* On-cluster etcd with TLS, [RBAC](https://kubernetes.io/docs/admin/authorization/rbac/)-enabled, [network policy](https://kubernetes.io/docs/concepts/services-networking/network-policies/)
-* Advanced features like [worker pools](https://typhoon.psdn.io/advanced/worker-pools/), [spot](https://typhoon.psdn.io/cl/aws/#spot) workers, and [snippets](https://typhoon.psdn.io/advanced/customization/#container-linux) customization
-* Ready for Ingress, Prometheus, Grafana, and other optional [addons](https://typhoon.psdn.io/addons/overview/)
-
-## Docs
-
-Please see the [official docs](https://typhoon.psdn.io) and the AWS [tutorial](https://typhoon.psdn.io/cl/aws/).
-
+See https://github.com/kinvolk/lokomotive-kubernetes/

diff:
-# Typhoon
-
-Typhoon is a minimal and free Kubernetes distribution.
-
-* Minimal, stable base Kubernetes distribution
-* Declarative infrastructure and configuration
-* Free (freedom and cost) and privacy-respecting
-* Practical for labs, datacenters, and clouds
-
-Typhoon distributes upstream Kubernetes, architectural conventions, and cluster addons, much like a GNU/Linux distribution provides the Linux kernel and userspace components.
-
-## Features
-
-* Kubernetes v1.14.1 (upstream, via [kubernetes-incubator/bootkube](https://github.com/kubernetes-incubator/bootkube))
-* Single or multi-master, [flannel](https://github.com/coreos/flannel) networking
-* On-cluster etcd with TLS, [RBAC](https://kubernetes.io/docs/admin/authorization/rbac/)-enabled
-* Advanced features like [worker pools](https://typhoon.psdn.io/advanced/worker-pools/), [low-priority](https://typhoon.psdn.io/cl/azure/#low-priority) workers, and [snippets](https://typhoon.psdn.io/advanced/customization/#container-linux) customization
-* Ready for Ingress, Prometheus, Grafana, and other optional [addons](https://typhoon.psdn.io/addons/overview/)
-
-## Docs
-
-Please see the [official docs](https://typhoon.psdn.io) and the Azure [tutorial](https://typhoon.psdn.io/cl/azure/).
-
+See https://github.com/kinvolk/lokomotive-kubernetes/

diff:
-# Typhoon
-
-Typhoon is a minimal and free Kubernetes distribution.
-
-* Minimal, stable base Kubernetes distribution
-* Declarative infrastructure and configuration
-* Free (freedom and cost) and privacy-respecting
-* Practical for labs, datacenters, and clouds
-
-Typhoon distributes upstream Kubernetes, architectural conventions, and cluster addons, much like a GNU/Linux distribution provides the Linux kernel and userspace components.
-
-## Features
-
-* Kubernetes v1.14.1 (upstream, via [kubernetes-incubator/bootkube](https://github.com/kubernetes-incubator/bootkube))
-* Single or multi-master, [Calico](https://www.projectcalico.org/) or [flannel](https://github.com/coreos/flannel) networking
-* On-cluster etcd with TLS, [RBAC](https://kubernetes.io/docs/admin/authorization/rbac/)-enabled, [network policy](https://kubernetes.io/docs/concepts/services-networking/network-policies/)
-* Advanced features like [snippets](https://typhoon.psdn.io/advanced/customization/#container-linux) customization
-* Ready for Ingress, Prometheus, Grafana, and other optional [addons](https://typhoon.psdn.io/addons/overview/)
-
-## Docs
-
-Please see the [official docs](https://typhoon.psdn.io) and the bare-metal [tutorial](https://typhoon.psdn.io/cl/bare-metal/).
-
+See https://github.com/kinvolk/lokomotive-kubernetes/

diff:
-# Typhoon
-
-Typhoon is a minimal and free Kubernetes distribution.
-
-* Minimal, stable base Kubernetes distribution
-* Declarative infrastructure and configuration
-* Free (freedom and cost) and privacy-respecting
-* Practical for labs, datacenters, and clouds
-
-Typhoon distributes upstream Kubernetes, architectural conventions, and cluster addons, much like a GNU/Linux distribution provides the Linux kernel and userspace components.
-
-## Features
-
-* Kubernetes v1.14.1 (upstream, via [kubernetes-incubator/bootkube](https://github.com/kubernetes-incubator/bootkube))
-* Single or multi-master, [Calico](https://www.projectcalico.org/) or [flannel](https://github.com/coreos/flannel) networking
-* On-cluster etcd with TLS, [RBAC](https://kubernetes.io/docs/admin/authorization/rbac/)-enabled, [network policy](https://kubernetes.io/docs/concepts/services-networking/network-policies/)
-* Advanced features like [worker pools](https://typhoon.psdn.io/advanced/worker-pools/), [preemptible](https://typhoon.psdn.io/cl/google-cloud/#preemption) workers, and [snippets](https://typhoon.psdn.io/advanced/customization/#container-linux) customization
-* Ready for Ingress, Prometheus, Grafana, and other optional [addons](https://typhoon.psdn.io/addons/overview/)
-
-## Docs
-
-Please see the [official docs](https://typhoon.psdn.io) and the Google Cloud [tutorial](https://typhoon.psdn.io/cl/google-cloud/).
-
+See https://github.com/kinvolk/lokomotive-kubernetes/

diff:
-Read how to set up a cluster with libvirt on KVM in the [docs](/docs/flatcar-linux/kvm-libvirt.md).
+See https://github.com/kinvolk/lokomotive-kubernetes/

diff:
-# README
-
-This module installs a Lokomotive cluster to Packet.
+See https://github.com/kinvolk/lokomotive-kubernetes/


message: Don't mention the version in the docs readme

diff:
# AWS
-In this tutorial, we'll create a Kubernetes v1.14.1 cluster on AWS with Flatcar Linux.
+In this tutorial, we'll create a Kubernetes cluster on AWS with Flatcar Linux.
We'll declare a Kubernetes cluster using the Lokomotive Terraform module. Then apply the changes to create a VPC, gateway, subnets, security groups, controller instances, worker auto-scaling group, network load balancer, and TLS assets.

diff:
!!! danger
Lokomotive for Azure is alpha. For production, use AWS or bare-metal. As Azure matures, check [errata](https://github.com/poseidon/typhoon/wiki/Errata) for known shortcomings.
-In this tutorial, we'll create a Kubernetes v1.14.1 cluster on Azure with Flatcar Linux.
+In this tutorial, we'll create a Kubernetes cluster on Azure with Flatcar Linux.
We'll declare a Kubernetes cluster using the Lokomotive Terraform module. Then apply the changes to create a resource group, virtual network, subnets, security groups, controller availability set, worker scale set, load balancer, and TLS assets.

diff:
# Bare-Metal
-In this tutorial, we'll network boot and provision a Kubernetes v1.14.1 cluster on bare-metal with Flatcar Linux.
+In this tutorial, we'll network boot and provision a Kubernetes cluster on bare-metal with Flatcar Linux.
First, we'll deploy a [Matchbox](https://github.com/poseidon/matchbox) service and setup a network boot environment. Then, we'll declare a Kubernetes cluster using the Lokomotive Terraform module and power on machines. On PXE boot, machines will install Flatcar Linux to disk, reboot into the disk install, and provision themselves as Kubernetes controllers or workers via Ignition.

diff:
# Google Cloud
-In this tutorial, we'll create a Kubernetes v1.14.1 cluster on Google Compute Engine with Flatcar Linux.
+In this tutorial, we'll create a Kubernetes cluster on Google Compute Engine with Flatcar Linux.
We'll declare a Kubernetes cluster using the Lokomotive Terraform module. Then apply the changes to create a network, firewall rules, health checks, controller instances, worker managed instance group, load balancers, and TLS assets.

diff:
# Packet
-In this tutorial, we'll create a Kubernetes v1.14.1 cluster on [Packet](https://packet.net) with [Flatcar Linux](https://www.flatcar-linux.org/). For external DNS, [Route53](https://aws.amazon.com/route53/) will be used.
+In this tutorial, we'll create a Kubernetes cluster on [Packet](https://packet.net) with [Flatcar Linux](https://www.flatcar-linux.org/). For external DNS, [Route53](https://aws.amazon.com/route53/) will be used.
We'll declare a Kubernetes cluster using the Lokomotive Terraform module. Then apply the changes to create DNS records, controller machines, worker machines and TLS assets.


message:
build: Add MOD argument to set -mod=MOD to build from vendor files
When make MOD=vendor is used this passes -mod=vendor instead of the
default -mod=readonly to go build.

old_path: Makefile

new_path: Makefile

diff:
TAG := `git describe --tags --always`
VERSION :=
+# MOD can either be "readonly" or "vendor".
+# The default is "readonly" which fetches the sources to the Go module cache.
+# Use make MOD=vendor to build with sources from the vendor directory instead.
+MOD ?= readonly
## Adds a '-dirty' suffix to version string if there are uncommitted changes
changes := $(shell git status --porcelain)
@@ -36,6 +40,7 @@ update-assets:
# we rely on defaults.
build-slim:
CGO_ENABLED=0 GOOS=linux GO111MODULE=on go build \
+ -mod=$(MOD) \
-ldflags $(LDFLAGS) \
-buildmode=exe \
github.com/kinvolk/lokoctl
@@ -70,6 +75,7 @@ install: update-lk-submodule update-assets install-slim
# so we rely on defaults.
install-slim:
CGO_ENABLED=0 GOOS=linux GO111MODULE=on go install \
+ -mod=$(MOD) \
-ldflags $(LDFLAGS) \
-buildmode=exe


message:
baremetal: Fix interpolation error from terraform escaping change
Terraform only needs ${} to be escaped as $${} and now does not
replace $$SOMETHING to $SOMETHING anymore as before, so we have
to either use $SOMETHING or $${SOMETHING}.

diff:
@@ -160,7 +160,7 @@ storage:
--mount volume=assets,target=/assets \
--volume bootstrap,kind=host,source=/etc/kubernetes \
--mount volume=bootstrap,target=/etc/kubernetes \
- $$RKT_OPTS \
+ $${RKT_OPTS} \
quay.io/coreos/bootkube:v0.14.0 \
--net=host \
--dns=host \


message: packet: Use Ignition config for iPXE installation

diff:
@@ -44,7 +44,7 @@ resource "packet_device" "controllers" {
operating_system = "${var.ipxe_script_url != "" ? "custom_ipxe" : format("flatcar_%s", var.os_channel)}"
billing_cycle = "hourly"
project_id = "${var.project_id}"
- user_data = "${element(data.ct_config.controller-ignitions.*.rendered, count.index)}"
+ user_data = "${var.ipxe_script_url != "" ? element(data.ct_config.controller-install-ignitions.*.rendered, count.index) : element(data.ct_config.controller-ignitions.*.rendered, count.index)}"
# If not present in the map, it uses ${var.reservation_ids_default}.
hardware_reservation_id = "${lookup(var.reservation_ids, format("controller-%v", count.index), var.reservation_ids_default)}"

diff:
@@ -8,12 +8,16 @@ resource "packet_device" "nodes" {
project_id = "${var.project_id}"
ipxe_script_url = "${var.ipxe_script_url}"
always_pxe = "false"
- user_data = "${data.ct_config.ignitions.rendered}"
+ user_data = "${var.ipxe_script_url != "" ? data.ct_config.install-ignitions.rendered : data.ct_config.ignitions.rendered}"
# If not present in the map, it uses ${var.reservation_ids_default}.
hardware_reservation_id = "${lookup(var.reservation_ids, format("worker-%v", count.index), var.reservation_ids_default)}"
}
+data "ct_config" "install-ignitions" {
+ content = "${data.template_file.install.rendered}"
+}
+
# These configs are used for the fist boot, to run flatcar-install
data "template_file" "install" {
template = "${file("${path.module}/cl/install.yaml.tmpl")}"


message: packet: Use multi-arch container image for calico-hostendpoint-controller

diff:
@@ -27,7 +27,7 @@ spec:
spec:
serviceAccountName: calico-hostendpoint-controller
containers:
- - image: quay.io/kinvolk/calico-hostendpoint-controller:v0.0.2
+ - image: kinvolk/calico-hostendpoint-controller:v0.0.2
name: calico-hostendpoint-controller
volumeMounts:
- mountPath: /tmp/


message:
packet: os_(arch|channel|version), ipxe_script_url for both worker/controller
By error some of the variables were still present for either worker or
controller during the time they were actually absent in Lokomotive.

diff:
@@ -63,8 +63,14 @@ cluster "packet" {
dns_zone_id = "XXX"
# Change Packet server location
facility = "ams1"
+ # Boot via iPXE (optional but currently needed for ARM; 'https://raw.githubusercontent.com/kinvolk/flatcar-ipxe-scripts/arm64-usr/packet.ipxe')
+ # ipxe_script_url = ""
+ # Define the CPU architecture (optional; 'amd64', 'arm64')
+ # os_arch = "amd64"
# Define a Flatcar Container Linux channel ('stable', 'beta', 'alpha' or 'edge')
os_channel = "stable"
+ # Define a Flatcar Container Linux version (optional)
+ # os_version = "current"
# Change Packet project ID
project_id = "aaa-bbb-ccc-ddd"
# Change management SSH public key
@@ -116,6 +122,12 @@ cluster "packet" {
# Define an instance type (optional)
# node_type = "t1.small.x86"
+ # Boot via iPXE (optional but currently needed for ARM; 'https://raw.githubusercontent.com/kinvolk/flatcar-ipxe-scripts/arm64-usr/packet.ipxe')
+ # ipxe_script_url = ""
+
+ # Define the CPU architecture (optional; 'amd64', 'arm64')
+ # os_arch = "amd64"
+
# Define a Flatcar Container Linux channel (optional; 'stable', 'beta', 'alpha' or 'edge')
# os_channel = "stable"

diff:
@@ -20,6 +20,8 @@ import (
type workerPool struct {
Name string `hcl:"pool_name,label"`
Count int `hcl:"count"`
+ IPXEScriptURL string `hcl:"ipxe_script_url,optional"`
+ OSArch string `hcl:"os_arch,optional"`
OSChannel string `hcl:"os_channel,optional"`
OSVersion string `hcl:"os_version,optional"`
NodeType string `hcl:"node_type,optional"`
@@ -44,7 +46,9 @@ type config struct {
Facility string `hcl:"facility"`
ProjectID string `hcl:"project_id"`
SSHPubKeys []string `hcl:"ssh_pubkeys"`
+ OSArch string `hcl:"os_arch,optional"`
OSChannel string `hcl:"os_channel,optional"`
+ OSVersion string `hcl:"os_version,optional"`
IPXEScriptURL string `hcl:"ipxe_script_url,optional"`
ManagementCIDRs []string `hcl:"management_cidrs"`
NodePrivateCIDR string `hcl:"node_private_cidr"`

diff:
@@ -32,9 +32,15 @@ module "packet-{{.Config.ClusterName}}" {
controller_type = "{{ .Config.ControllerType }}"
{{- end }}
+ {{- if .Config.OSArch }}
+ os_arch = "{{ .Config.OSArch }}"
+ {{- end }}
{{- if .Config.OSChannel }}
os_channel = "{{ .Config.OSChannel }}"
{{- end }}
+ {{- if .Config.OSVersion }}
+ os_version = "{{ .Config.OSVersion }}"
+ {{- end }}
{{- if .Config.IPXEScriptURL }}
ipxe_script_url = "{{ .Config.IPXEScriptURL }}"
@@ -107,6 +113,9 @@ module "worker-pool-{{ $index }}" {
ipxe_script_url = "{{ $.Config.IPXEScriptURL }}"
{{- end }}
+ {{- if $pool.OSArch }}
+ os_arch = "{{ $pool.OSArch }}"
+ {{- end }}
{{- if $pool.OSChannel }}
os_channel = "{{ $pool.OSChannel }}"
{{- end }}


message:
packet: Remove temporary ARM signature key
Since Flatcar Container Linux now has official
ARM64 releases, the developer signature workaround
is not needed anymore and hinders accepting offically
signed images.

diff:
@@ -40,20 +40,12 @@ storage:
contents:
inline: |
#!/bin/bash -ex
- if [ "${os_arch}" = arm64 ]; then
- curl -o /tmp/Flatcar_ARM_Testing_Key.asc \
- https://www.flatcar-linux.org/security/image-signing-key/Flatcar_ARM_Testing_Key.asc
- ARG="-d /dev/sda -k /tmp/Flatcar_ARM_Testing_Key.asc"
- else
- ARG="-s"
- fi
-
flatcar-install \
-C "${os_channel}" \
-V "${os_version}" \
-o "${flatcar_linux_oem}" \
-i /opt/postinstall-ignition.json \
- $ARG
+ -s
udevadm settle
systemctl reboot
passwd:


message: docs: Rephrase the current state of arm64 until it's stable

diff:
@@ -241,7 +241,7 @@ Learn about [maintenance](../topics/maintenance.md).
## ARM Support and Hybrid Clusters
-Lokomotive and Flatcar support the Packet arm64 server types `c1.large.arm` and `c2.large.arm`.
+Lokomotive and Flatcar currently include an alpha-quality preview for the Packet arm64 server types `c1.large.arm` and `c2.large.arm`.
They can be used for both worker and controller nodes.
Besides specifying them in `controller_type`/`type` you need to configure some additional variables
in the respective controller/worker module:


message: ci/baremetal: Use same asset folder path for all platforms

diff:
# Note: number of worker nodes has to match `pxe-testbed --num-nodes ...` count
cluster "bare-metal" {
- asset_dir = "${pathexpand("~/.lokoctl/mercury")}"
+ asset_dir = "${pathexpand("~/lokoctl-assets")}"
ssh_pubkeys = ["${file(pathexpand("~/.ssh/id_rsa.pub"))}"]
cached_install = "true"
matchbox_ca_path = "${pathexpand("~/pxe-testbed/.matchbox/ca.crt")}"


message:
ci: Add ARM cluster configuration for Packet
The cluster configuration is the same as for the existing
Packet cluster. The only difference is the server type
c2.large.arm (controller) and c1.large.arm (worker),
the added variables os_arch/os_channel/ipxe_script_url,
and the worker count set to 1 instead of 2.

change_type: ADD

diff:
+cluster "packet" {
+ asset_dir = pathexpand("~/lokoctl-assets")
+ aws_region = "$AWS_DEFAULT_REGION"
+ cluster_name = "$CLUSTER_ID"
+ controller_count = 1
+ dns_zone = "$AWS_DNS_ZONE"
+
+ dns_zone_id = "$AWS_DNS_ZONE_ID"
+ facility = "sjc1"
+
+ project_id = "$PACKET_PROJECT_ID"
+
+ ipxe_script_url = "https://raw.githubusercontent.com/kinvolk/flatcar-ipxe-scripts/arm64-usr/packet.ipxe"
+ os_arch = "arm64"
+ os_channel = "alpha"
+ controller_type = "c2.large.arm"
+
+ ssh_pubkeys = ["$PUB_KEY"]
+ management_cidrs = ["0.0.0.0/0"]
+ node_private_cidr = "10.0.0.0/8"
+
+ worker_pool "pool-1" {
+ count = 1
+ ipxe_script_url = "https://raw.githubusercontent.com/kinvolk/flatcar-ipxe-scripts/arm64-usr/packet.ipxe"
+ os_arch = "arm64"
+ os_channel = "alpha"
+ node_type = "c1.large.arm"
+ }
+}


message: bare-metal: Update Matchbox Terraform Provider

diff:
provider "matchbox" {
- version = "0.2.3"
+ version = "0.3.0"
ca = "${file(pathexpand("~/pxe-testbed/.matchbox/ca.crt"))}"
client_cert = "${file(pathexpand("~/pxe-testbed/.matchbox/client.crt"))}"
client_key = "${file(pathexpand("~/pxe-testbed/.matchbox/client.key"))}"

diff:
@@ -117,9 +117,9 @@ Terraform v0.11.13
Add the [terraform-provider-matchbox](https://github.com/poseidon/terraform-provider-matchbox) plugin binary for your system to `~/.terraform.d/plugins/`, noting the final name.
```sh
-wget https://github.com/poseidon/terraform-provider-matchbox/releases/download/v0.2.3/terraform-provider-matchbox-v0.2.3-linux-amd64.tar.gz
-tar xzf terraform-provider-matchbox-v0.2.3-linux-amd64.tar.gz
-mv terraform-provider-matchbox-v0.2.3-linux-amd64/terraform-provider-matchbox ~/.terraform.d/plugins/terraform-provider-matchbox_v0.2.3
+wget https://github.com/poseidon/terraform-provider-matchbox/releases/download/v0.3.0/terraform-provider-matchbox-v0.3.0-linux-amd64.tar.gz
+tar xzf terraform-provider-matchbox-v0.3.0-linux-amd64.tar.gz
+mv terraform-provider-matchbox-v0.3.0-linux-amd64/terraform-provider-matchbox ~/.terraform.d/plugins/terraform-provider-matchbox_v0.3.0
```
Add the [terraform-provider-ct](https://github.com/poseidon/terraform-provider-ct) plugin binary for your system to `~/.terraform.d/plugins/`, noting the final name.
@@ -142,7 +142,7 @@ Configure the Matchbox provider to use your Matchbox API endpoint and client cer
```tf
provider "matchbox" {
- version = "0.2.3"
+ version = "0.3.0"
endpoint = "matchbox.example.com:8081"
client_cert = "${file("~/.config/matchbox/client.crt")}"
client_key = "${file("~/.config/matchbox/client.key")}"


message:
ci: remove string interpolation
The string interpolation syntax is not required with HCL 2.

diff:
# Note: number of worker nodes has to match `pxe-testbed --num-nodes ...` count
cluster "bare-metal" {
- asset_dir = "${pathexpand("~/lokoctl-assets")}"
- ssh_pubkeys = ["${file(pathexpand("~/.ssh/id_rsa.pub"))}"]
+ asset_dir = pathexpand("~/lokoctl-assets")
+ ssh_pubkeys = [file(pathexpand("~/.ssh/id_rsa.pub"))]
cached_install = "true"
- matchbox_ca_path = "${pathexpand("~/pxe-testbed/.matchbox/ca.crt")}"
- matchbox_client_cert_path = "${pathexpand("~/pxe-testbed/.matchbox/client.crt")}"
- matchbox_client_key_path = "${pathexpand("~/pxe-testbed/.matchbox/client.key")}"
+ matchbox_ca_path = pathexpand("~/pxe-testbed/.matchbox/ca.crt")
+ matchbox_client_cert_path = pathexpand("~/pxe-testbed/.matchbox/client.crt")
+ matchbox_client_key_path = pathexpand("~/pxe-testbed/.matchbox/client.key")
matchbox_endpoint = "matchbox.example.com:8081"
matchbox_http_endpoint = "http://matchbox.example.com:8080"
cluster_name = "mercury"


message:
ci: Switch Packet ARM worker type to c2.large.arm
The c1.large.arm is currently not coming up after
installation.

diff:
@@ -24,6 +24,6 @@ cluster "packet" {
ipxe_script_url = "https://raw.githubusercontent.com/kinvolk/flatcar-ipxe-scripts/arm64-usr/packet.ipxe"
os_arch = "arm64"
os_channel = "alpha"
- node_type = "c1.large.arm"
+ node_type = "c2.large.arm"
}
}


message: ci: Add kvm-libvirt configuration

diff:
+module "controller" {
+ source = "../../kvm-libvirt/flatcar-linux/kubernetes"
+
+ providers = {
+ local = local.default
+ null = null.default
+ template = template.default
+ tls = tls.default
+ libvirt = libvirt.default
+ }
+
+ os_image_unpacked = "file:///var/tmp/flatcar_production_qemu_image.img"
+ ssh_keys = ["$PUB_KEY"]
+
+ asset_dir = pathexpand("~/assets")
+
+ machine_domain = "vmcluster.k8s"
+ cluster_name = "vmcluster"
+ node_ip_pool = "192.168.192.0/24"
+
+ controller_count = 1
+}
+
+module "worker-pool-one" {
+ source = "../../kvm-libvirt/flatcar-linux/kubernetes/workers"
+
+ providers = {
+ local = local.default
+ template = template.default
+ tls = tls.default
+ libvirt = libvirt.default
+ }
+
+ ssh_keys = module.controller.ssh_keys
+
+ machine_domain = module.controller.machine_domain
+ cluster_name = module.controller.cluster_name
+ libvirtpool = module.controller.libvirtpool
+ libvirtbaseid = module.controller.libvirtbaseid
+
+ pool_name = "one"
+
+ worker_count = 1
+
+ kubeconfig = module.controller.kubeconfig
+}

diff:
+provider "ct" {
+ version = "0.4.0"
+}
+
+provider "local" {
+ version = "~> 1.2"
+ alias = "default"
+}
+
+provider "null" {
+ version = "~> 2.1"
+ alias = "default"
+}
+
+provider "template" {
+ version = "~> 2.1"
+ alias = "default"
+}
+
+provider "tls" {
+ version = "~> 2.0"
+ alias = "default"
+}
+
+provider "libvirt" {
+ version = "~> 0.6.1"
+ uri = "qemu:///system"
+ alias = "default"
+}

original_message: ci: Add kvm-libvirt configuration