Class: Fig::Protocol::Artifactory

Inherits:
Object
  • Object
show all
Includes:
Fig::Protocol, NetRCEnabled
Defined in:
lib/fig/protocol/artifactory.rb

Overview

file transfers/storage using https as the transport and artifactory as the backing store

Constant Summary collapse

BROWSER_API_PATH =

Artifactory browser API endpoint (undocumented API)

'ui/api/v1/ui/v2/nativeBrowser/'
INITIAL_LIST_FETCH_SIZE =

Default number of list entries to fetch on initial iteration

20000

Instance Method Summary collapse

Constructor Details

#initializeArtifactory

Returns a new instance of Artifactory.



35
36
37
# File 'lib/fig/protocol/artifactory.rb', line 35

def initialize
  initialize_netrc
end

Instance Method Details

#download(art_uri, path, prompt_for_login) ⇒ Object



82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
# File 'lib/fig/protocol/artifactory.rb', line 82

def download(art_uri, path, )
  Fig::Logging.info("Downloading from artifactory: #{art_uri}")

  uri = httpify_uri(art_uri)

  # Log equivalent curl command for debugging
  authentication = get_authentication_for(uri.host, )
  if authentication
    Fig::Logging.debug("Equivalent curl: curl -u #{authentication.username}:*** -o '#{path}' '#{uri}'")
  else
    Fig::Logging.debug("Equivalent curl: curl -o '#{path}' '#{uri}'")
  end

  ::File.open(path, 'wb') do |file|
    file.binmode

    begin
      download_via_http_get(uri.to_s, file)
    rescue SystemCallError => error
      Fig::Logging.debug error.message
      raise Fig::FileNotFoundError.new error.message, uri
    rescue SocketError => error
      Fig::Logging.debug error.message
      raise Fig::FileNotFoundError.new error.message, uri
    end
  end

  return true
end

#download_list(uri) ⇒ Object

must return a list of strings in the form <package_name>/<version>



40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
# File 'lib/fig/protocol/artifactory.rb', line 40

def download_list(uri)
  Fig::Logging.info("Downloading list of packages at #{uri}")
  package_versions = []

  begin
    # Parse URI to extract base endpoint and repository key
    # Expected format: https://artifacts.example.com/artifactory/repo-name/
    parse_uri(uri) => { repo_key:, base_endpoint: }
    
    # Create Artifactory client instance
    authentication = get_authentication_for(uri.host, :prompt_for_login)
    client_config = { endpoint: base_endpoint }
    if authentication
      client_config[:username] = authentication.username
      client_config[:password] = authentication.password
    end
    client = ::Artifactory::Client.new(client_config)

    # Use Artifactory browser API to list directories at repo root
    list_url = URI.join(base_endpoint, BROWSER_API_PATH, "#{repo_key}/")
    
    packages = get_all_artifactory_entries(list_url, client)
    
    # Filter to only valid package names upfront
    valid_packages = packages.select do |package_item|
      package_item['folder'] && 
      package_item['name'] =~ Fig::PackageDescriptor::COMPONENT_PATTERN
    end
    
    Fig::Logging.debug("Found #{valid_packages.size} valid packages, fetching versions concurrently...")
    
    # Use concurrent requests to fetch version lists (major performance improvement)
    package_versions = fetch_versions_concurrently(valid_packages, base_endpoint, repo_key, client_config)
    
  rescue => e
    # Follow FTP pattern: log error but don't fail completely
    Fig::Logging.debug("Could not retrieve package list from #{uri}: #{e.message}")
  end

  return package_versions.sort
end

#path_up_to_date?(uri, path, prompt_for_login) ⇒ Boolean

we can know this most of the time with the stat api

Returns:

  • (Boolean)


158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
# File 'lib/fig/protocol/artifactory.rb', line 158

def path_up_to_date?(uri, path, )
  Fig::Logging.info("Checking if #{path} is up to date at #{uri}")

  begin
    parse_uri(uri) => { repo_key:, base_endpoint:, target_path: }


    # Create Artifactory client instance (same as upload method)
    authentication = get_authentication_for(uri.host, )
    client_config = { endpoint: base_endpoint }
    if authentication
      client_config[:username] = authentication.username
      client_config[:password] = authentication.password
    end
    client = ::Artifactory::Client.new(client_config)
    
    # use storage api instead of search - more reliable for virtual repos
    storage_url = "/api/storage/#{repo_key}/#{target_path}"
    
    response = client.get(storage_url)
    
    # compare sizes first
    remote_size = response['size'].to_i
    if remote_size != ::File.size(path)
      # TODO VERBOSE
      return false
    end
    
    # compare modification times
    remote_mtime = Time.parse(response['lastModified'])
    local_mtime = ::File.mtime(path)
    
    if remote_mtime <= local_mtime
      return true
    end

    # TODO VERBOSE
    return false
  rescue => error
    Fig::Logging.debug "Error checking if #{path} is up to date: #{error.message}"
    return nil
  end
end

#upload(local_file, uri) ⇒ Object



112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
# File 'lib/fig/protocol/artifactory.rb', line 112

def upload(local_file, uri)
  Fig::Logging.info("Uploading #{local_file} to artifactory at #{uri}")

  begin
    parse_uri(uri) => { repo_key:, base_endpoint:, target_path: }

    # Configure Artifactory gem globally - unlike other methods that can use client instances,
    # the artifact.upload() method ignores the client: parameter and only uses global config.
    # This is a limitation of the artifactory gem's upload implementation.
    authentication = get_authentication_for(uri.host, :prompt_for_login)
    ::Artifactory.configure do |config|
      config.endpoint = base_endpoint
      if authentication
        config.username = authentication.username
        config.password = authentication.password
      end
    end

    # Log equivalent curl command for debugging
    if authentication
      Fig::Logging.debug("Equivalent curl: curl -u #{authentication.username}:*** -T '#{local_file}' '#{uri}'")
    else
      Fig::Logging.debug("Equivalent curl: curl -T '#{local_file}' '#{uri}'")
    end

    # Create artifact and upload
    artifact = ::Artifactory::Resource::Artifact.new(local_path: local_file)
    
    # Collect metadata for upload
     = (local_file, target_path, uri)

    # Upload with metadata (no client parameter needed - uses global config)
    artifact.upload(repo_key, target_path, )
    
    Fig::Logging.info("Successfully uploaded #{local_file} to #{uri}")
    
  rescue ArgumentError => e
    # Let ArgumentError bubble up for invalid URIs
    raise e
  rescue => e
    Fig::Logging.debug("Upload failed: #{e.message}")
    raise Fig::NetworkError.new("Failed to upload #{local_file} to #{uri}: #{e.message}")
  end
end